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