gitee.com/liuxuezhan/go-micro-v1.18.0@v1.0.0/api/handler/broker/broker.go (about) 1 // Package broker provides a go-micro/broker handler 2 package broker 3 4 import ( 5 "encoding/json" 6 "io/ioutil" 7 "net/http" 8 "net/url" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/gorilla/websocket" 14 "gitee.com/liuxuezhan/go-micro-v1.18.0/api/handler" 15 "gitee.com/liuxuezhan/go-micro-v1.18.0/broker" 16 "gitee.com/liuxuezhan/go-micro-v1.18.0/util/log" 17 ) 18 19 const ( 20 Handler = "broker" 21 22 pingTime = (readDeadline * 9) / 10 23 readLimit = 16384 24 readDeadline = 60 * time.Second 25 writeDeadline = 10 * time.Second 26 ) 27 28 type brokerHandler struct { 29 opts handler.Options 30 u websocket.Upgrader 31 } 32 33 type conn struct { 34 b broker.Broker 35 cType string 36 topic string 37 queue string 38 exit chan bool 39 40 sync.Mutex 41 ws *websocket.Conn 42 } 43 44 var ( 45 once sync.Once 46 contentType = "text/plain" 47 ) 48 49 func checkOrigin(r *http.Request) bool { 50 origin := r.Header["Origin"] 51 if len(origin) == 0 { 52 return true 53 } 54 u, err := url.Parse(origin[0]) 55 if err != nil { 56 return false 57 } 58 return u.Host == r.Host 59 } 60 61 func (c *conn) close() { 62 select { 63 case <-c.exit: 64 return 65 default: 66 close(c.exit) 67 } 68 } 69 70 func (c *conn) readLoop() { 71 defer func() { 72 c.close() 73 c.ws.Close() 74 }() 75 76 // set read limit/deadline 77 c.ws.SetReadLimit(readLimit) 78 c.ws.SetReadDeadline(time.Now().Add(readDeadline)) 79 80 // set close handler 81 ch := c.ws.CloseHandler() 82 c.ws.SetCloseHandler(func(code int, text string) error { 83 err := ch(code, text) 84 c.close() 85 return err 86 }) 87 88 // set pong handler 89 c.ws.SetPongHandler(func(string) error { 90 c.ws.SetReadDeadline(time.Now().Add(readDeadline)) 91 return nil 92 }) 93 94 for { 95 _, message, err := c.ws.ReadMessage() 96 if err != nil { 97 return 98 } 99 c.b.Publish(c.topic, &broker.Message{ 100 Header: map[string]string{"Content-Type": c.cType}, 101 Body: message, 102 }) 103 } 104 } 105 106 func (c *conn) write(mType int, data []byte) error { 107 c.Lock() 108 c.ws.SetWriteDeadline(time.Now().Add(writeDeadline)) 109 err := c.ws.WriteMessage(mType, data) 110 c.Unlock() 111 return err 112 } 113 114 func (c *conn) writeLoop() { 115 ticker := time.NewTicker(pingTime) 116 117 var opts []broker.SubscribeOption 118 119 if len(c.queue) > 0 { 120 opts = append(opts, broker.Queue(c.queue)) 121 } 122 123 subscriber, err := c.b.Subscribe(c.topic, func(p broker.Event) error { 124 b, err := json.Marshal(p.Message()) 125 if err != nil { 126 return nil 127 } 128 return c.write(websocket.TextMessage, b) 129 }, opts...) 130 131 defer func() { 132 subscriber.Unsubscribe() 133 ticker.Stop() 134 c.ws.Close() 135 }() 136 137 if err != nil { 138 log.Log(err.Error()) 139 return 140 } 141 142 for { 143 select { 144 case <-ticker.C: 145 if err := c.write(websocket.PingMessage, []byte{}); err != nil { 146 return 147 } 148 case <-c.exit: 149 return 150 } 151 } 152 } 153 154 func (b *brokerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 155 br := b.opts.Service.Client().Options().Broker 156 157 // Setup the broker 158 once.Do(func() { 159 br.Init() 160 br.Connect() 161 }) 162 163 // Parse 164 r.ParseForm() 165 topic := r.Form.Get("topic") 166 167 // Can't do anything without a topic 168 if len(topic) == 0 { 169 http.Error(w, "Topic not specified", 400) 170 return 171 } 172 173 // Post assumed to be Publish 174 if r.Method == "POST" { 175 // Create a broker message 176 msg := &broker.Message{ 177 Header: make(map[string]string), 178 } 179 180 // Set header 181 for k, v := range r.Header { 182 msg.Header[k] = strings.Join(v, ", ") 183 } 184 185 // Read body 186 b, err := ioutil.ReadAll(r.Body) 187 if err != nil { 188 http.Error(w, err.Error(), 500) 189 return 190 } 191 192 // Set body 193 msg.Body = b 194 195 // Publish 196 br.Publish(topic, msg) 197 return 198 } 199 200 // now back to our regularly scheduled programming 201 202 if r.Method != "GET" { 203 http.Error(w, "Method not allowed", 405) 204 return 205 } 206 207 queue := r.Form.Get("queue") 208 209 ws, err := b.u.Upgrade(w, r, nil) 210 if err != nil { 211 log.Log(err.Error()) 212 return 213 } 214 215 cType := r.Header.Get("Content-Type") 216 if len(cType) == 0 { 217 cType = contentType 218 } 219 220 c := &conn{ 221 b: br, 222 cType: cType, 223 topic: topic, 224 queue: queue, 225 exit: make(chan bool), 226 ws: ws, 227 } 228 229 go c.writeLoop() 230 c.readLoop() 231 } 232 233 func (b *brokerHandler) String() string { 234 return "broker" 235 } 236 237 func NewHandler(opts ...handler.Option) handler.Handler { 238 return &brokerHandler{ 239 u: websocket.Upgrader{ 240 CheckOrigin: func(r *http.Request) bool { 241 return true 242 }, 243 ReadBufferSize: 1024, 244 WriteBufferSize: 1024, 245 }, 246 opts: handler.NewOptions(opts...), 247 } 248 } 249 250 func WithCors(cors map[string]bool, opts ...handler.Option) handler.Handler { 251 return &brokerHandler{ 252 u: websocket.Upgrader{ 253 CheckOrigin: func(r *http.Request) bool { 254 if origin := r.Header.Get("Origin"); cors[origin] { 255 return true 256 } else if len(origin) > 0 && cors["*"] { 257 return true 258 } else if checkOrigin(r) { 259 return true 260 } 261 return false 262 }, 263 ReadBufferSize: 1024, 264 WriteBufferSize: 1024, 265 }, 266 opts: handler.NewOptions(opts...), 267 } 268 }