github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/app/web_conn.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"fmt"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/mattermost/mattermost-server/model"
    12  	"github.com/mattermost/mattermost-server/utils"
    13  
    14  	l4g "github.com/alecthomas/log4go"
    15  	"github.com/gorilla/websocket"
    16  	goi18n "github.com/nicksnyder/go-i18n/i18n"
    17  )
    18  
    19  const (
    20  	SEND_QUEUE_SIZE           = 256
    21  	SEND_SLOW_WARN            = (SEND_QUEUE_SIZE * 50) / 100
    22  	SEND_DEADLOCK_WARN        = (SEND_QUEUE_SIZE * 95) / 100
    23  	WRITE_WAIT                = 30 * time.Second
    24  	PONG_WAIT                 = 100 * time.Second
    25  	PING_PERIOD               = (PONG_WAIT * 6) / 10
    26  	AUTH_TIMEOUT              = 5 * time.Second
    27  	WEBCONN_MEMBER_CACHE_TIME = 1000 * 60 * 30 // 30 minutes
    28  )
    29  
    30  type WebConn struct {
    31  	sessionExpiresAt          int64 // This should stay at the top for 64-bit alignment of 64-bit words accessed atomically
    32  	App                       *App
    33  	WebSocket                 *websocket.Conn
    34  	Send                      chan model.WebSocketMessage
    35  	sessionToken              atomic.Value
    36  	session                   atomic.Value
    37  	UserId                    string
    38  	T                         goi18n.TranslateFunc
    39  	Locale                    string
    40  	AllChannelMembers         map[string]string
    41  	LastAllChannelMembersTime int64
    42  	Sequence                  int64
    43  	endWritePump              chan struct{}
    44  	pumpFinished              chan struct{}
    45  }
    46  
    47  func (a *App) NewWebConn(ws *websocket.Conn, session model.Session, t goi18n.TranslateFunc, locale string) *WebConn {
    48  	if len(session.UserId) > 0 {
    49  		a.Go(func() {
    50  			a.SetStatusOnline(session.UserId, session.Id, false)
    51  			a.UpdateLastActivityAtIfNeeded(session)
    52  		})
    53  	}
    54  
    55  	wc := &WebConn{
    56  		App:          a,
    57  		Send:         make(chan model.WebSocketMessage, SEND_QUEUE_SIZE),
    58  		WebSocket:    ws,
    59  		UserId:       session.UserId,
    60  		T:            t,
    61  		Locale:       locale,
    62  		endWritePump: make(chan struct{}, 2),
    63  		pumpFinished: make(chan struct{}, 1),
    64  	}
    65  
    66  	wc.SetSession(&session)
    67  	wc.SetSessionToken(session.Token)
    68  	wc.SetSessionExpiresAt(session.ExpiresAt)
    69  
    70  	return wc
    71  }
    72  
    73  func (wc *WebConn) Close() {
    74  	wc.WebSocket.Close()
    75  	wc.endWritePump <- struct{}{}
    76  	<-wc.pumpFinished
    77  }
    78  
    79  func (c *WebConn) GetSessionExpiresAt() int64 {
    80  	return atomic.LoadInt64(&c.sessionExpiresAt)
    81  }
    82  
    83  func (c *WebConn) SetSessionExpiresAt(v int64) {
    84  	atomic.StoreInt64(&c.sessionExpiresAt, v)
    85  }
    86  
    87  func (c *WebConn) GetSessionToken() string {
    88  	return c.sessionToken.Load().(string)
    89  }
    90  
    91  func (c *WebConn) SetSessionToken(v string) {
    92  	c.sessionToken.Store(v)
    93  }
    94  
    95  func (c *WebConn) GetSession() *model.Session {
    96  	return c.session.Load().(*model.Session)
    97  }
    98  
    99  func (c *WebConn) SetSession(v *model.Session) {
   100  	if v != nil {
   101  		v = v.DeepCopy()
   102  	}
   103  
   104  	c.session.Store(v)
   105  }
   106  
   107  func (c *WebConn) Pump() {
   108  	ch := make(chan struct{}, 1)
   109  	go func() {
   110  		c.writePump()
   111  		ch <- struct{}{}
   112  	}()
   113  	c.readPump()
   114  	c.endWritePump <- struct{}{}
   115  	<-ch
   116  	c.App.HubUnregister(c)
   117  	c.pumpFinished <- struct{}{}
   118  }
   119  
   120  func (c *WebConn) readPump() {
   121  	defer func() {
   122  		c.WebSocket.Close()
   123  	}()
   124  	c.WebSocket.SetReadLimit(model.SOCKET_MAX_MESSAGE_SIZE_KB)
   125  	c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
   126  	c.WebSocket.SetPongHandler(func(string) error {
   127  		c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
   128  		if c.IsAuthenticated() {
   129  			c.App.Go(func() {
   130  				c.App.SetStatusAwayIfNeeded(c.UserId, false)
   131  			})
   132  		}
   133  		return nil
   134  	})
   135  
   136  	for {
   137  		var req model.WebSocketRequest
   138  		if err := c.WebSocket.ReadJSON(&req); err != nil {
   139  			// browsers will appear as CloseNoStatusReceived
   140  			if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
   141  				l4g.Debug(fmt.Sprintf("websocket.read: client side closed socket userId=%v", c.UserId))
   142  			} else {
   143  				l4g.Debug(fmt.Sprintf("websocket.read: closing websocket for userId=%v error=%v", c.UserId, err.Error()))
   144  			}
   145  
   146  			return
   147  		} else {
   148  			c.App.Srv.WebSocketRouter.ServeWebSocket(c, &req)
   149  		}
   150  	}
   151  }
   152  
   153  func (c *WebConn) writePump() {
   154  	ticker := time.NewTicker(PING_PERIOD)
   155  	authTicker := time.NewTicker(AUTH_TIMEOUT)
   156  
   157  	defer func() {
   158  		ticker.Stop()
   159  		authTicker.Stop()
   160  		c.WebSocket.Close()
   161  	}()
   162  
   163  	for {
   164  		select {
   165  		case msg, ok := <-c.Send:
   166  			if !ok {
   167  				c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
   168  				c.WebSocket.WriteMessage(websocket.CloseMessage, []byte{})
   169  				return
   170  			}
   171  
   172  			evt, evtOk := msg.(*model.WebSocketEvent)
   173  
   174  			skipSend := false
   175  			if len(c.Send) >= SEND_SLOW_WARN {
   176  				// When the pump starts to get slow we'll drop non-critical messages
   177  				if msg.EventType() == model.WEBSOCKET_EVENT_TYPING ||
   178  					msg.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE ||
   179  					msg.EventType() == model.WEBSOCKET_EVENT_CHANNEL_VIEWED {
   180  					l4g.Info(fmt.Sprintf("websocket.slow: dropping message userId=%v type=%v channelId=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId))
   181  					skipSend = true
   182  				}
   183  			}
   184  
   185  			if !skipSend {
   186  				var msgBytes []byte
   187  				if evtOk {
   188  					cpyEvt := &model.WebSocketEvent{}
   189  					*cpyEvt = *evt
   190  					cpyEvt.Sequence = c.Sequence
   191  					msgBytes = []byte(cpyEvt.ToJson())
   192  					c.Sequence++
   193  				} else {
   194  					msgBytes = []byte(msg.ToJson())
   195  				}
   196  
   197  				if len(c.Send) >= SEND_DEADLOCK_WARN {
   198  					if evtOk {
   199  						l4g.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v channelId=%v size=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId, len(msg.ToJson())))
   200  					} else {
   201  						l4g.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v size=%v", c.UserId, msg.EventType(), len(msg.ToJson())))
   202  					}
   203  				}
   204  
   205  				c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
   206  				if err := c.WebSocket.WriteMessage(websocket.TextMessage, msgBytes); err != nil {
   207  					// browsers will appear as CloseNoStatusReceived
   208  					if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
   209  						l4g.Debug(fmt.Sprintf("websocket.send: client side closed socket userId=%v", c.UserId))
   210  					} else {
   211  						l4g.Debug(fmt.Sprintf("websocket.send: closing websocket for userId=%v, error=%v", c.UserId, err.Error()))
   212  					}
   213  
   214  					return
   215  				}
   216  
   217  				if c.App.Metrics != nil {
   218  					c.App.Go(func() {
   219  						c.App.Metrics.IncrementWebSocketBroadcast(msg.EventType())
   220  					})
   221  				}
   222  
   223  			}
   224  		case <-ticker.C:
   225  			c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
   226  			if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
   227  				// browsers will appear as CloseNoStatusReceived
   228  				if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
   229  					l4g.Debug(fmt.Sprintf("websocket.ticker: client side closed socket userId=%v", c.UserId))
   230  				} else {
   231  					l4g.Debug(fmt.Sprintf("websocket.ticker: closing websocket for userId=%v error=%v", c.UserId, err.Error()))
   232  				}
   233  
   234  				return
   235  			}
   236  		case <-c.endWritePump:
   237  			return
   238  		case <-authTicker.C:
   239  			if c.GetSessionToken() == "" {
   240  				l4g.Debug(fmt.Sprintf("websocket.authTicker: did not authenticate ip=%v", c.WebSocket.RemoteAddr()))
   241  				return
   242  			}
   243  			authTicker.Stop()
   244  		}
   245  	}
   246  }
   247  
   248  func (webCon *WebConn) InvalidateCache() {
   249  	webCon.AllChannelMembers = nil
   250  	webCon.LastAllChannelMembersTime = 0
   251  	webCon.SetSession(nil)
   252  	webCon.SetSessionExpiresAt(0)
   253  }
   254  
   255  func (webCon *WebConn) IsAuthenticated() bool {
   256  	// Check the expiry to see if we need to check for a new session
   257  	if webCon.GetSessionExpiresAt() < model.GetMillis() {
   258  		if webCon.GetSessionToken() == "" {
   259  			return false
   260  		}
   261  
   262  		session, err := webCon.App.GetSession(webCon.GetSessionToken())
   263  		if err != nil {
   264  			l4g.Error(utils.T("api.websocket.invalid_session.error"), err.Error())
   265  			webCon.SetSessionToken("")
   266  			webCon.SetSession(nil)
   267  			webCon.SetSessionExpiresAt(0)
   268  			return false
   269  		}
   270  
   271  		webCon.SetSession(session)
   272  		webCon.SetSessionExpiresAt(session.ExpiresAt)
   273  	}
   274  
   275  	return true
   276  }
   277  
   278  func (webCon *WebConn) SendHello() {
   279  	msg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_HELLO, "", "", webCon.UserId, nil)
   280  	msg.Add("server_version", fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, webCon.App.ClientConfigHash(), utils.IsLicensed()))
   281  	webCon.Send <- msg
   282  }
   283  
   284  func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
   285  	// IMPORTANT: Do not send event if WebConn does not have a session
   286  	if !webCon.IsAuthenticated() {
   287  		return false
   288  	}
   289  
   290  	// If the event is destined to a specific user
   291  	if len(msg.Broadcast.UserId) > 0 {
   292  		if webCon.UserId == msg.Broadcast.UserId {
   293  			return true
   294  		} else {
   295  			return false
   296  		}
   297  	}
   298  
   299  	// if the user is omitted don't send the message
   300  	if len(msg.Broadcast.OmitUsers) > 0 {
   301  		if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok {
   302  			return false
   303  		}
   304  	}
   305  
   306  	// Only report events to users who are in the channel for the event
   307  	if len(msg.Broadcast.ChannelId) > 0 {
   308  		if model.GetMillis()-webCon.LastAllChannelMembersTime > WEBCONN_MEMBER_CACHE_TIME {
   309  			webCon.AllChannelMembers = nil
   310  			webCon.LastAllChannelMembersTime = 0
   311  		}
   312  
   313  		if webCon.AllChannelMembers == nil {
   314  			if result := <-webCon.App.Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true); result.Err != nil {
   315  				l4g.Error("webhub.shouldSendEvent: " + result.Err.Error())
   316  				return false
   317  			} else {
   318  				webCon.AllChannelMembers = result.Data.(map[string]string)
   319  				webCon.LastAllChannelMembersTime = model.GetMillis()
   320  			}
   321  		}
   322  
   323  		if _, ok := webCon.AllChannelMembers[msg.Broadcast.ChannelId]; ok {
   324  			return true
   325  		} else {
   326  			return false
   327  		}
   328  	}
   329  
   330  	// Only report events to users who are in the team for the event
   331  	if len(msg.Broadcast.TeamId) > 0 {
   332  		return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
   333  
   334  	}
   335  
   336  	return true
   337  }
   338  
   339  func (webCon *WebConn) IsMemberOfTeam(teamId string) bool {
   340  
   341  	currentSession := webCon.GetSession()
   342  
   343  	if currentSession == nil || len(currentSession.Token) == 0 {
   344  		session, err := webCon.App.GetSession(webCon.GetSessionToken())
   345  		if err != nil {
   346  			l4g.Error(utils.T("api.websocket.invalid_session.error"), err.Error())
   347  			return false
   348  		} else {
   349  			webCon.SetSession(session)
   350  			currentSession = session
   351  		}
   352  	}
   353  
   354  	member := currentSession.GetTeamByTeamId(teamId)
   355  
   356  	if member != nil {
   357  		return true
   358  	} else {
   359  		return false
   360  	}
   361  }