wide/session/sessions.go

372 lines
9.3 KiB
Go
Raw Normal View History

2014-11-12 18:13:14 +03:00
// Copyright (c) 2014, B3log
2014-11-20 06:30:18 +03:00
//
2014-11-12 18:13:14 +03:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2014-11-20 06:30:18 +03:00
//
2014-11-12 18:13:14 +03:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-11-20 06:30:18 +03:00
//
2014-11-12 18:13:14 +03:00
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2014-10-29 13:15:18 +03:00
// Session manipulations.
2014-09-25 09:37:59 +04:00
//
2014-10-29 13:15:18 +03:00
// Wide server side needs maintain two kinds of sessions:
2014-09-25 09:29:04 +04:00
//
2014-10-29 13:15:18 +03:00
// 1. HTTP session: mainly used for login authentication
// 2. Wide session: browser tab open/refresh will create one, and associates with HTTP session
2014-09-17 10:35:48 +04:00
//
2014-10-29 13:15:18 +03:00
// When a session gone: release all resources associated with it, such as running processes, event queues.
2014-09-17 10:35:48 +04:00
package session
import (
"bytes"
2014-09-22 19:13:07 +04:00
"encoding/json"
2014-09-19 20:56:32 +04:00
"net/http"
2014-09-19 15:21:13 +04:00
"os"
2014-11-21 06:19:57 +03:00
"strconv"
2014-09-17 10:35:48 +04:00
"sync"
"time"
2014-09-22 19:13:07 +04:00
"github.com/b3log/wide/conf"
2014-09-19 15:21:13 +04:00
"github.com/b3log/wide/event"
2014-09-19 20:56:32 +04:00
"github.com/b3log/wide/util"
2014-09-17 10:35:48 +04:00
"github.com/golang/glog"
"github.com/gorilla/sessions"
2014-09-19 20:56:32 +04:00
"github.com/gorilla/websocket"
2014-09-17 10:35:48 +04:00
)
const (
2014-10-29 13:15:18 +03:00
SessionStateActive = iota // session state: active
SessionStateClosed // session state: closed (not used so far)
2014-09-17 10:35:48 +04:00
)
2014-09-20 06:39:29 +04:00
var (
2014-10-29 13:15:18 +03:00
// Session channels. <sid, *util.WSChannel>
2014-09-25 05:51:00 +04:00
SessionWS = map[string]*util.WSChannel{}
2014-09-20 06:39:29 +04:00
2014-10-29 13:15:18 +03:00
// Editor channels. <sid, *util.WSChannel>
EditorWS = map[string]*util.WSChannel{}
// Output channels. <sid, *util.WSChannel>
2014-09-20 06:39:29 +04:00
OutputWS = map[string]*util.WSChannel{}
2014-10-29 13:15:18 +03:00
// Notification channels. <sid, *util.WSChannel>
2014-09-20 06:39:29 +04:00
NotificationWS = map[string]*util.WSChannel{}
)
2014-09-19 20:56:32 +04:00
2014-10-29 13:15:18 +03:00
// HTTP session store.
2014-09-17 10:35:48 +04:00
var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
2014-10-29 13:15:18 +03:00
// Wide session, associated with a browser tab.
2014-09-17 10:35:48 +04:00
type WideSession struct {
2014-10-29 13:15:18 +03:00
Id string // id
Username string // username
HTTPSession *sessions.Session // HTTP session related
Processes []*os.Process // process set
EventQueue *event.UserEventQueue // event queue
State int // state
Content *conf.LatestSessionContent // the latest session content
Created time.Time // create time
Updated time.Time // the latest use time
2014-09-17 10:35:48 +04:00
}
2014-10-29 13:15:18 +03:00
// Type of wide sessions.
2014-09-17 10:35:48 +04:00
type Sessions []*WideSession
2014-10-29 13:15:18 +03:00
// Wide sessions.
2014-09-17 10:35:48 +04:00
var WideSessions Sessions
2014-10-29 13:15:18 +03:00
// Exclusive lock.
2014-09-17 10:35:48 +04:00
var mutex sync.Mutex
2014-10-29 13:15:18 +03:00
// In some special cases (such as a browser uninterrupted refresh / refresh in the source code view) will occur
// some invalid sessions, the function checks and removes these invalid sessions periodically (1 hour).
2014-09-25 09:37:59 +04:00
//
2014-10-29 13:15:18 +03:00
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
2014-09-23 17:03:44 +04:00
func FixedTimeRelease() {
go func() {
2014-10-10 10:24:47 +04:00
for _ = range time.Tick(time.Hour) {
2014-09-23 17:03:44 +04:00
hour, _ := time.ParseDuration("-30m")
threshold := time.Now().Add(hour)
for _, s := range WideSessions {
if s.Updated.Before(threshold) {
2014-11-20 12:13:01 +03:00
glog.V(3).Infof("Removes a invalid session [%s], user [%s]", s.Id, s.Username)
2014-09-23 17:03:44 +04:00
WideSessions.Remove(s.Id)
}
}
}
}()
}
2014-11-21 06:19:57 +03:00
// Online user statistic report.
type userReport struct {
username string
sessionCnt int
updated time.Time
}
// report returns a online user statistics in pretty format.
func (u *userReport) report() string {
2014-11-21 09:43:58 +03:00
return "[" + u.username + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions, latest activity [" +
2014-11-21 06:19:57 +03:00
u.updated.Format("2006-01-02 15:04:05") + "]"
}
// FixedTimeReport reports the Wide sessions status periodically (10 minutes).
func FixedTimeReport() {
go func() {
for _ = range time.Tick(10 * time.Minute) {
users := map[string]*userReport{} // <username, *userReport>
for _, s := range WideSessions {
if report, exists := users[s.Username]; exists {
if s.Updated.After(report.updated) {
users[s.Username].updated = s.Updated
}
report.sessionCnt++
} else {
users[s.Username] = &userReport{username: s.Username, sessionCnt: 1, updated: s.Updated}
}
}
var buf bytes.Buffer
buf.WriteString("\n [" + strconv.Itoa(len(users)) + "] users are online and [" + strconv.Itoa(len(WideSessions)) +
2014-11-21 09:43:58 +03:00
"] sessions currently\n")
2014-11-21 06:19:57 +03:00
for _, t := range users {
buf.WriteString(" " + t.report() + "\n")
2014-11-21 06:19:57 +03:00
}
glog.Info(buf.String())
2014-11-21 06:19:57 +03:00
}
}()
}
2014-10-29 13:15:18 +03:00
// WSHandler handles request of creating session channel.
//
// When a channel closed, releases all resources associated with it.
2014-09-19 20:56:32 +04:00
func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0]
wSession := WideSessions.Get(sid)
if nil == wSession {
2014-11-02 10:44:24 +03:00
httpSession, _ := HTTPSession.Get(r, "wide-session")
2014-09-19 20:56:32 +04:00
2014-11-02 10:44:24 +03:00
if httpSession.IsNew {
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
2014-11-20 12:04:18 +03:00
wSession = WideSessions.New(httpSession, sid)
2014-11-02 10:44:24 +03:00
2014-11-20 09:57:38 +03:00
glog.Infof("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username)
2014-09-19 20:56:32 +04:00
}
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
2014-11-02 10:44:24 +03:00
ret := map[string]interface{}{"output": "Session initialized", "cmd": "init-session"}
2014-11-20 08:59:08 +03:00
err := wsChan.WriteJSON(&ret)
2014-11-20 06:30:18 +03:00
if nil != err {
return
}
SessionWS[sid] = &wsChan
2014-09-19 20:56:32 +04:00
2014-09-25 05:51:00 +04:00
glog.V(4).Infof("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
2014-09-19 20:56:32 +04:00
input := map[string]interface{}{}
for {
2014-11-20 09:11:54 +03:00
if err := wsChan.ReadJSON(&input); err != nil {
2014-11-20 17:53:54 +03:00
glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]",
sid, wSession.Username)
2014-09-19 20:56:32 +04:00
2014-09-23 17:03:44 +04:00
WideSessions.Remove(sid)
2014-09-19 20:56:32 +04:00
2014-09-23 17:03:44 +04:00
return
2014-09-19 20:56:32 +04:00
}
ret = map[string]interface{}{"output": "", "cmd": "session-output"}
2014-11-20 08:59:08 +03:00
if err := wsChan.WriteJSON(&ret); err != nil {
2014-09-19 20:56:32 +04:00
glog.Error("Session WS ERROR: " + err.Error())
2014-11-20 17:53:54 +03:00
2014-09-19 20:56:32 +04:00
return
}
wsChan.Time = time.Now()
}
}
2014-10-29 13:15:18 +03:00
// SaveContent handles request of session content storing.
2014-09-22 19:13:07 +04:00
func SaveContent(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
2014-09-23 07:20:01 +04:00
args := struct {
2014-09-23 17:03:44 +04:00
Sid string
2014-09-23 07:20:01 +04:00
*conf.LatestSessionContent
}{}
2014-09-22 19:13:07 +04:00
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
2014-09-23 17:03:44 +04:00
wSession := WideSessions.Get(args.Sid)
2014-09-22 19:13:07 +04:00
if nil == wSession {
data["succ"] = false
return
}
2014-09-23 07:20:01 +04:00
wSession.Content = args.LatestSessionContent
2014-09-22 19:13:07 +04:00
for _, user := range conf.Wide.Users {
if user.Name == wSession.Username {
2014-10-29 13:15:18 +03:00
// update the variable in-memory, conf.FixedTimeSave() function will persist it periodically
2014-09-22 19:13:07 +04:00
user.LatestSessionContent = wSession.Content
2014-09-23 17:03:44 +04:00
wSession.Refresh()
2014-09-22 19:13:07 +04:00
return
}
}
}
2014-10-29 13:15:18 +03:00
// SetProcesses binds process set with the wide session.
2014-09-19 15:21:13 +04:00
func (s *WideSession) SetProcesses(ps []*os.Process) {
s.Processes = ps
s.Refresh()
}
2014-10-29 13:15:18 +03:00
// Refresh refreshes the channel by updating its use time.
2014-09-19 15:21:13 +04:00
func (s *WideSession) Refresh() {
s.Updated = time.Now()
}
2014-10-29 13:15:18 +03:00
// New creates a wide session.
2014-11-02 10:44:24 +03:00
func (sessions *Sessions) New(httpSession *sessions.Session, sid string) *WideSession {
2014-09-17 10:35:48 +04:00
mutex.Lock()
defer mutex.Unlock()
now := time.Now()
2014-11-02 10:44:24 +03:00
// create user event queuselect
userEventQueue := event.UserEventQueues.New(sid)
2014-09-19 15:21:13 +04:00
2014-09-17 10:35:48 +04:00
ret := &WideSession{
2014-11-02 10:44:24 +03:00
Id: sid,
2014-09-22 19:13:07 +04:00
Username: httpSession.Values["username"].(string),
2014-09-17 10:35:48 +04:00
HTTPSession: httpSession,
2014-09-19 15:21:13 +04:00
EventQueue: userEventQueue,
2014-09-17 10:35:48 +04:00
State: SessionStateActive,
2014-09-22 19:13:07 +04:00
Content: &conf.LatestSessionContent{},
2014-09-17 10:35:48 +04:00
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
return ret
}
2014-10-29 13:15:18 +03:00
// Get gets a wide session with the specified session id.
2014-09-19 15:21:13 +04:00
func (sessions *Sessions) Get(sid string) *WideSession {
mutex.Lock()
defer mutex.Unlock()
for _, s := range *sessions {
if s.Id == sid {
return s
}
}
return nil
}
2014-10-29 13:15:18 +03:00
// Remove removes a wide session specified with the given session id, releases resources associated with it.
2014-09-25 09:37:59 +04:00
//
2014-10-29 13:15:18 +03:00
// Session-related resources:
2014-09-25 09:29:04 +04:00
//
2014-10-29 13:15:18 +03:00
// 1. user event queue
// 2. process set
// 3. websocket channels
2014-09-17 10:35:48 +04:00
func (sessions *Sessions) Remove(sid string) {
mutex.Lock()
defer mutex.Unlock()
for i, s := range *sessions {
if s.Id == sid {
2014-10-29 13:15:18 +03:00
// remove from session set
2014-09-17 10:35:48 +04:00
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
2014-10-29 13:15:18 +03:00
// close user event queue
2014-09-23 17:03:44 +04:00
event.UserEventQueues.Close(sid)
2014-10-29 13:15:18 +03:00
// kill processes
2014-09-23 17:03:44 +04:00
for _, p := range s.Processes {
if err := p.Kill(); nil != err {
2014-11-20 12:13:01 +03:00
glog.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
2014-09-23 17:03:44 +04:00
} else {
2014-11-20 12:13:01 +03:00
glog.V(3).Infof("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
2014-09-23 17:03:44 +04:00
}
}
2014-10-29 13:15:18 +03:00
// close websocket channels
2014-09-23 17:03:44 +04:00
if ws, ok := OutputWS[sid]; ok {
ws.Close()
delete(OutputWS, sid)
}
if ws, ok := NotificationWS[sid]; ok {
ws.Close()
delete(NotificationWS, sid)
}
2014-09-25 05:51:00 +04:00
if ws, ok := SessionWS[sid]; ok {
2014-09-23 17:03:44 +04:00
ws.Close()
2014-09-25 05:51:00 +04:00
delete(SessionWS, sid)
2014-09-23 17:03:44 +04:00
}
2014-10-29 13:15:18 +03:00
cnt := 0 // count wide sessions associated with HTTP session
2014-09-23 17:03:44 +04:00
for _, s := range *sessions {
if s.HTTPSession.ID == s.HTTPSession.ID {
cnt++
}
}
2014-10-29 13:15:18 +03:00
2014-11-20 12:13:01 +03:00
glog.V(3).Infof("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.Username, cnt)
2014-09-19 20:56:32 +04:00
return
2014-09-17 10:35:48 +04:00
}
}
}
2014-10-29 13:15:18 +03:00
// GetByUsername gets wide sessions.
2014-09-24 07:00:33 +04:00
func (sessions *Sessions) GetByUsername(username string) []*WideSession {
2014-09-17 10:35:48 +04:00
mutex.Lock()
defer mutex.Unlock()
ret := []*WideSession{}
for _, s := range *sessions {
2014-09-24 07:00:33 +04:00
if s.Username == username {
2014-09-17 10:35:48 +04:00
ret = append(ret, s)
}
}
return ret
}