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  }