github.com/gdamore/mangos@v1.4.0/transport/ws/ws.go (about) 1 // Copyright 2018 The Mangos Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use file except in compliance with the License. 5 // You may obtain a copy of the license at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package ws implements a simple WebSocket transport for mangos. 16 // This transport is considered EXPERIMENTAL. 17 package ws 18 19 import ( 20 "crypto/tls" 21 "net" 22 "net/http" 23 "net/url" 24 "strings" 25 "sync" 26 27 "github.com/gorilla/websocket" 28 29 "nanomsg.org/go-mangos" 30 ) 31 32 // Some special options 33 const ( 34 // OptionWebSocketMux is a retrieve-only property used to obtain 35 // the *http.ServeMux instance associated with the server. This 36 // can be used to subsequently register additional handlers for 37 // different URIs. This option is only valid on a Listener. 38 // Generally you use this option when you want to use the standard 39 // mangos Listen() method to start up the server. 40 OptionWebSocketMux = "WEBSOCKET-MUX" 41 42 // OptionWebSocketHandler is used to obtain the underlying 43 // http.Handler (websocket.Server) object, so you can use this 44 // on your own http.Server instances. It is a gross error to use 45 // the value returned by this method on an http server if the 46 // server is also started with mangos Listen(). This means that you 47 // will use at most either this option, or OptionWebSocketMux, but 48 // never both. This option is only valid on a listener. 49 OptionWebSocketHandler = "WEBSOCKET-HANDLER" 50 51 // OptionWebSocketCheckOrigin controls the check of the origin of the 52 // underlying Listener (websocket.Upgrader). 53 // Excerpt from https://godoc.org/github.com/gorilla/websocket: 54 // Web browsers allow Javascript applications to open a WebSocket 55 // connection to any host. It's up to the server to enforce an origin 56 // policy using the Origin request header sent by the browser. The 57 // Upgrader calls the function specified in the CheckOrigin field to 58 // check the origin. If the CheckOrigin function returns false, then 59 // the Upgrade method fails the WebSocket handshake with HTTP status 60 // 403. If the CheckOrigin field is nil, then the Upgrader uses a safe 61 // default: fail the handshake if the Origin request header is present 62 // and not equal to the Host request header. An application can allow 63 // connections from any origin by specifying a function that always 64 // returns true: 65 // 66 // var upgrader = websocket.Upgrader{ 67 // CheckOrigin: func(r *http.Request) bool { return true }, 68 // } 69 // 70 // The deprecated Upgrade function does not enforce an origin policy. 71 // It's the application's responsibility to check the Origin header 72 // before calling Upgrade. 73 OptionWebSocketCheckOrigin = "WEBSOCKET-CHECKORIGIN" 74 ) 75 76 type options map[string]interface{} 77 78 // GetOption retrieves an option value. 79 func (o options) get(name string) (interface{}, error) { 80 if o == nil { 81 return nil, mangos.ErrBadOption 82 } 83 v, ok := o[name] 84 if !ok { 85 return nil, mangos.ErrBadOption 86 } 87 return v, nil 88 } 89 90 // SetOption sets an option. We have none, so just ErrBadOption. 91 func (o options) set(name string, val interface{}) error { 92 switch name { 93 case mangos.OptionNoDelay: 94 fallthrough 95 case mangos.OptionKeepAlive: 96 switch v := val.(type) { 97 case bool: 98 o[name] = v 99 return nil 100 default: 101 return mangos.ErrBadValue 102 } 103 case mangos.OptionTLSConfig: 104 switch v := val.(type) { 105 case *tls.Config: 106 o[name] = v 107 return nil 108 default: 109 return mangos.ErrBadValue 110 } 111 case OptionWebSocketCheckOrigin: 112 switch v := val.(type) { 113 case bool: 114 o[name] = v 115 return nil 116 default: 117 return mangos.ErrBadValue 118 } 119 } 120 return mangos.ErrBadOption 121 } 122 123 // wsPipe implements the Pipe interface on a websocket 124 type wsPipe struct { 125 ws *websocket.Conn 126 proto mangos.Protocol 127 addr string 128 open bool 129 wg sync.WaitGroup 130 props map[string]interface{} 131 iswss bool 132 dtype int 133 sync.Mutex 134 } 135 136 type wsTran int 137 138 func (w *wsPipe) Recv() (*mangos.Message, error) { 139 140 // We ignore the message type for receive. 141 _, body, err := w.ws.ReadMessage() 142 if err != nil { 143 return nil, err 144 } 145 msg := mangos.NewMessage(0) 146 msg.Body = body 147 return msg, nil 148 } 149 150 func (w *wsPipe) Send(m *mangos.Message) error { 151 152 var buf []byte 153 154 if m.Expired() { 155 m.Free() 156 return nil 157 } 158 if len(m.Header) > 0 { 159 buf = make([]byte, 0, len(m.Header)+len(m.Body)) 160 buf = append(buf, m.Header...) 161 buf = append(buf, m.Body...) 162 } else { 163 buf = m.Body 164 } 165 if err := w.ws.WriteMessage(w.dtype, buf); err != nil { 166 return err 167 } 168 m.Free() 169 return nil 170 } 171 172 func (w *wsPipe) LocalProtocol() uint16 { 173 return w.proto.Number() 174 } 175 176 func (w *wsPipe) RemoteProtocol() uint16 { 177 return w.proto.PeerNumber() 178 } 179 180 func (w *wsPipe) Close() error { 181 w.Lock() 182 defer w.Unlock() 183 if w.IsOpen() { 184 w.open = false 185 w.ws.Close() 186 w.wg.Done() 187 } 188 return nil 189 } 190 191 func (w *wsPipe) IsOpen() bool { 192 return w.open 193 } 194 195 func (w *wsPipe) GetProp(name string) (interface{}, error) { 196 if v, ok := w.props[name]; ok { 197 return v, nil 198 } 199 return nil, mangos.ErrBadProperty 200 } 201 202 type dialer struct { 203 addr string // url 204 proto mangos.Protocol 205 opts options 206 iswss bool 207 maxrx int 208 } 209 210 func (d *dialer) Dial() (mangos.Pipe, error) { 211 var w *wsPipe 212 213 wd := &websocket.Dialer{} 214 215 wd.Subprotocols = []string{d.proto.PeerName() + ".sp.nanomsg.org"} 216 if v, ok := d.opts[mangos.OptionTLSConfig]; ok { 217 wd.TLSClientConfig = v.(*tls.Config) 218 } 219 220 w = &wsPipe{proto: d.proto, addr: d.addr, open: true} 221 w.dtype = websocket.BinaryMessage 222 w.props = make(map[string]interface{}) 223 224 var err error 225 if w.ws, _, err = wd.Dial(d.addr, nil); err != nil { 226 return nil, err 227 } 228 w.ws.SetReadLimit(int64(d.maxrx)) 229 w.props[mangos.PropLocalAddr] = w.ws.LocalAddr() 230 w.props[mangos.PropRemoteAddr] = w.ws.RemoteAddr() 231 if tlsConn, ok := w.ws.UnderlyingConn().(*tls.Conn); ok { 232 w.props[mangos.PropTLSConnState] = tlsConn.ConnectionState() 233 } 234 235 w.wg.Add(1) 236 return w, nil 237 } 238 239 func (d *dialer) SetOption(n string, v interface{}) error { 240 return d.opts.set(n, v) 241 } 242 243 func (d *dialer) GetOption(n string) (interface{}, error) { 244 return d.opts.get(n) 245 } 246 247 type listener struct { 248 pending []*wsPipe 249 lock sync.Mutex 250 cv sync.Cond 251 running bool 252 noserve bool 253 addr string 254 ug websocket.Upgrader 255 htsvr *http.Server 256 mux *http.ServeMux 257 url *url.URL 258 listener net.Listener 259 proto mangos.Protocol 260 opts options 261 iswss bool 262 maxrx int 263 } 264 265 func (l *listener) SetOption(n string, v interface{}) error { 266 switch n { 267 case OptionWebSocketCheckOrigin: 268 if v, ok := v.(bool); ok { 269 if !v { 270 l.ug.CheckOrigin = func(r *http.Request) bool { return true } 271 } 272 } 273 } 274 return l.opts.set(n, v) 275 } 276 277 func (l *listener) GetOption(n string) (interface{}, error) { 278 switch n { 279 case OptionWebSocketMux: 280 return l.mux, nil 281 case OptionWebSocketHandler: 282 // Caller intends to use use in his own server, so mark 283 // us running. If he didn't mean this, the side effect is 284 // that Accept() will appear to hang, even though Listen() 285 // is not called yet. 286 l.running = true 287 l.noserve = true 288 return l, nil 289 case OptionWebSocketCheckOrigin: 290 if v, err := l.opts.get(n); err == nil { 291 if v, ok := v.(bool); ok { 292 return v, nil 293 } 294 } 295 return true, nil 296 297 } 298 return l.opts.get(n) 299 } 300 301 func (l *listener) Listen() error { 302 var taddr *net.TCPAddr 303 var err error 304 var tcfg *tls.Config 305 306 if l.noserve { 307 // The HTTP framework is going to call us, so we use that rather than 308 // listening on our own. We just fake this out. 309 return nil 310 } 311 if l.iswss { 312 v, ok := l.opts[mangos.OptionTLSConfig] 313 if !ok || v == nil { 314 return mangos.ErrTLSNoConfig 315 } 316 tcfg = v.(*tls.Config) 317 if tcfg.Certificates == nil || len(tcfg.Certificates) == 0 { 318 return mangos.ErrTLSNoCert 319 } 320 } 321 322 // We listen separately, that way we can catch and deal with the 323 // case of a port already in use. This also lets us configure 324 // properties of the underlying TCP connection. 325 326 if taddr, err = mangos.ResolveTCPAddr(l.url.Host); err != nil { 327 return err 328 } 329 330 if tlist, err := net.ListenTCP("tcp", taddr); err != nil { 331 return err 332 } else if l.iswss { 333 l.listener = tls.NewListener(tlist, tcfg) 334 } else { 335 l.listener = tlist 336 } 337 l.pending = nil 338 l.running = true 339 340 l.htsvr = &http.Server{Addr: l.url.Host, Handler: l.mux} 341 342 go l.htsvr.Serve(l.listener) 343 344 return nil 345 } 346 347 func (l *listener) Accept() (mangos.Pipe, error) { 348 var w *wsPipe 349 350 l.lock.Lock() 351 defer l.lock.Unlock() 352 353 for { 354 if !l.running { 355 return nil, mangos.ErrClosed 356 } 357 if len(l.pending) == 0 { 358 l.cv.Wait() 359 continue 360 } 361 w = l.pending[len(l.pending)-1] 362 l.pending = l.pending[:len(l.pending)-1] 363 break 364 } 365 366 return w, nil 367 } 368 369 func (l *listener) handler(ws *websocket.Conn, req *http.Request) { 370 l.lock.Lock() 371 372 if !l.running { 373 ws.Close() 374 l.lock.Unlock() 375 return 376 } 377 378 if ws.Subprotocol() != l.proto.Name()+".sp.nanomsg.org" { 379 ws.Close() 380 l.lock.Unlock() 381 return 382 } 383 384 w := &wsPipe{ws: ws, addr: l.addr, proto: l.proto, open: true} 385 w.dtype = websocket.BinaryMessage 386 w.iswss = l.iswss 387 w.ws.SetReadLimit(int64(l.maxrx)) 388 389 w.props = make(map[string]interface{}) 390 w.props[mangos.PropLocalAddr] = ws.LocalAddr() 391 w.props[mangos.PropRemoteAddr] = ws.RemoteAddr() 392 393 if req.TLS != nil { 394 w.props[mangos.PropTLSConnState] = *req.TLS 395 } 396 397 w.wg.Add(1) 398 l.pending = append(l.pending, w) 399 l.cv.Broadcast() 400 l.lock.Unlock() 401 402 // We must not return before the socket is closed, because 403 // our caller will close the websocket on our return. 404 w.wg.Wait() 405 } 406 407 func (l *listener) Handle(pattern string, handler http.Handler) { 408 l.mux.Handle(pattern, handler) 409 } 410 411 func (l *listener) HandleFunc(pattern string, handler http.HandlerFunc) { 412 l.mux.HandleFunc(pattern, handler) 413 } 414 415 func (l *listener) Close() error { 416 l.lock.Lock() 417 defer l.lock.Unlock() 418 if !l.running { 419 return mangos.ErrClosed 420 } 421 if l.listener != nil { 422 l.listener.Close() 423 } 424 l.running = false 425 l.cv.Broadcast() 426 for _, ws := range l.pending { 427 ws.Close() 428 } 429 l.pending = nil 430 return nil 431 } 432 433 func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { 434 ws, err := l.ug.Upgrade(w, r, nil) 435 if err != nil { 436 return 437 } 438 l.handler(ws, r) 439 } 440 441 func (l *listener) Address() string { 442 return l.url.String() 443 } 444 445 func (wsTran) Scheme() string { 446 return "ws" 447 } 448 449 func (wsTran) NewDialer(addr string, sock mangos.Socket) (mangos.PipeDialer, error) { 450 iswss := strings.HasPrefix(addr, "wss://") 451 opts := make(map[string]interface{}) 452 453 opts[mangos.OptionNoDelay] = true 454 opts[mangos.OptionKeepAlive] = true 455 proto := sock.GetProtocol() 456 maxrx := 0 457 if v, e := sock.GetOption(mangos.OptionMaxRecvSize); e == nil { 458 maxrx = v.(int) 459 } 460 461 return &dialer{addr: addr, proto: proto, iswss: iswss, opts: opts, maxrx: maxrx}, nil 462 } 463 464 func (t wsTran) NewListener(addr string, sock mangos.Socket) (mangos.PipeListener, error) { 465 proto := sock.GetProtocol() 466 l, e := t.listener(addr, proto) 467 if e == nil { 468 if v, e := sock.GetOption(mangos.OptionMaxRecvSize); e == nil { 469 l.maxrx = v.(int) 470 } 471 l.mux.Handle(l.url.Path, l) 472 } 473 return l, e 474 } 475 476 func (wsTran) listener(addr string, proto mangos.Protocol) (*listener, error) { 477 var err error 478 l := &listener{proto: proto, opts: make(map[string]interface{})} 479 l.cv.L = &l.lock 480 l.ug.Subprotocols = []string{proto.Name() + ".sp.nanomsg.org"} 481 482 if strings.HasPrefix(addr, "wss://") { 483 l.iswss = true 484 } 485 l.url, err = url.ParseRequestURI(addr) 486 if err != nil { 487 return nil, err 488 } 489 if len(l.url.Path) == 0 { 490 l.url.Path = "/" 491 } 492 l.mux = http.NewServeMux() 493 494 l.htsvr = &http.Server{Addr: l.url.Host, Handler: l.mux} 495 496 return l, nil 497 } 498 499 // NewTransport allocates a new ws:// transport. 500 func NewTransport() mangos.Transport { 501 return wsTran(0) 502 }