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  }