#218 and WebSocket bug fix

This commit is contained in:
Liang Ding 2015-09-27 07:36:34 +08:00
parent b758ec854e
commit a119370a23
10 changed files with 142 additions and 153 deletions

View File

@ -19,13 +19,11 @@ import (
"flag" "flag"
"html/template" "html/template"
"io" "io"
"math/rand"
"mime" "mime"
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
@ -222,11 +220,6 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
} }
httpSession.Save(r, w) httpSession.Save(r, w)
// create a Wide session
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
user := conf.GetUser(username) user := conf.GetUser(username)
if nil == user { if nil == user {
logger.Warnf("Not found user [%s]", username) logger.Warnf("Not found user [%s]", username)
@ -241,7 +234,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
wideSessions := session.WideSessions.GetByUsername(username) wideSessions := session.WideSessions.GetByUsername(username)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"session": wideSession, "latestSessionContent": user.LatestSessionContent, "username": username, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
"user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": util.Go.GetCrossPlatforms()} "user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": util.Go.GetCrossPlatforms()}

View File

@ -54,11 +54,6 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
// create a wide session
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
locale := conf.Wide.Locale locale := conf.Wide.Locale
// try to load file // try to load file
@ -92,7 +87,8 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
} }
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"session": wideSession, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer, "sid": session.WideSessions.GenId(), "pathSeparator": conf.PathSeparator,
"codeMirrorVer": conf.CodeMirrorVer,
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(), "code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(),
"embed": embed, "disqus": disqus, "fileName": fileName} "embed": embed, "disqus": disqus, "fileName": fileName}

View File

@ -25,6 +25,7 @@ package session
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"math/rand"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -193,21 +194,6 @@ func (f userReports) Less(i, j int) bool { return f[i].processCnt > f[j].process
// When a channel closed, releases all resources associated with it. // When a channel closed, releases all resources associated with it.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
wSession := WideSessions.Get(sid)
if nil == wSession {
httpSession, _ := HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
wSession = WideSessions.New(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username)
}
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()} wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
@ -220,6 +206,22 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
SessionWS[sid] = &wsChan SessionWS[sid] = &wsChan
wSession := WideSessions.Get(sid)
if nil == wSession {
httpSession, _ := HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
wSession = WideSessions.new(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username)
}
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS)) logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
input := map[string]interface{}{} input := map[string]interface{}{}
@ -297,114 +299,11 @@ func (s *WideSession) Refresh() {
s.Updated = time.Now() s.Updated = time.Now()
} }
// New creates a wide session. // GenId generates a wide session id.
func (sessions *wSessions) New(httpSession *sessions.Session, sid string) *WideSession { func (sessions *wSessions) GenId() string {
mutex.Lock() rand.Seed(time.Now().UnixNano())
defer mutex.Unlock()
username := httpSession.Values["username"].(string) return strconv.Itoa(rand.Int())
now := time.Now()
ret := &WideSession{
ID: sid,
Username: username,
HTTPSession: httpSession,
EventQueue: nil,
State: sessionStateActive,
Content: &conf.LatestSessionContent{},
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
if "playground" == username {
return ret
}
// create user event queue
ret.EventQueue = event.UserEventQueues.New(sid)
// add a filesystem watcher to notify front-end after the files changed
watcher, err := fsnotify.NewWatcher()
if err != nil {
logger.Error(err)
return ret
}
go func() {
defer util.Recover()
for {
ch := SessionWS[sid]
if nil == ch {
return // release this gorutine
}
select {
case event := <-watcher.Events:
path := event.Name
dir := filepath.Dir(path)
ch = SessionWS[sid]
if nil == ch {
return // release this gorutine
}
logger.Debug(event)
if event.Op&fsnotify.Create == fsnotify.Create {
if err = watcher.Add(path); nil != err {
logger.Warn(err, path)
}
logger.Tracef("File watcher added a file [%s]", path)
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file"}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file"}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file"}
ch.WriteJSON(&cmd)
}
case err := <-watcher.Errors:
if nil != err {
logger.Error("File watcher ERROR: ", err)
}
}
}
}()
go func() {
defer util.Recover()
workspaces := filepath.SplitList(conf.GetUserWorkspace(username))
for _, workspace := range workspaces {
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
if ".git" == f.Name() { // XXX: discard other unconcered dirs
return filepath.SkipDir
}
if f.IsDir() {
if err = watcher.Add(dirPath); nil != err {
logger.Error(err, dirPath)
}
logger.Tracef("File watcher added a dir [%s]", dirPath)
}
return nil
})
}
ret.FileWatcher = watcher
}()
return ret
} }
// Get gets a wide session with the specified session id. // Get gets a wide session with the specified session id.
@ -505,3 +404,111 @@ func (sessions *wSessions) GetByUsername(username string) []*WideSession {
return ret return ret
} }
// new creates a wide session.
func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideSession {
mutex.Lock()
defer mutex.Unlock()
username := httpSession.Values["username"].(string)
now := time.Now()
ret := &WideSession{
ID: sid,
Username: username,
HTTPSession: httpSession,
EventQueue: nil,
State: sessionStateActive,
Content: &conf.LatestSessionContent{},
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
if "playground" == username {
return ret
}
// create user event queue
ret.EventQueue = event.UserEventQueues.New(sid)
// add a filesystem watcher to notify front-end after the files changed
watcher, err := fsnotify.NewWatcher()
if err != nil {
logger.Error(err)
return ret
}
go func() {
defer util.Recover()
for {
ch := SessionWS[sid]
if nil == ch {
return // release this gorutine
}
select {
case event := <-watcher.Events:
path := event.Name
dir := filepath.Dir(path)
ch = SessionWS[sid]
if nil == ch {
return // release this gorutine
}
logger.Trace(event)
if event.Op&fsnotify.Create == fsnotify.Create {
if err = watcher.Add(path); nil != err {
logger.Warn(err, path)
}
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file"}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file"}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file"}
ch.WriteJSON(&cmd)
}
case err := <-watcher.Errors:
if nil != err {
logger.Error("File watcher ERROR: ", err)
}
}
}
}()
go func() {
defer util.Recover()
workspaces := filepath.SplitList(conf.GetUserWorkspace(username))
for _, workspace := range workspaces {
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
if ".git" == f.Name() { // XXX: discard other unconcered dirs
return filepath.SkipDir
}
if f.IsDir() {
if err = watcher.Add(dirPath); nil != err {
logger.Error(err, dirPath)
}
logger.Tracef("File watcher added a dir [%s]", dirPath)
}
return nil
})
}
ret.FileWatcher = watcher
}()
return ret
}

View File

@ -17,12 +17,10 @@ package shell
import ( import (
"html/template" "html/template"
"math/rand"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
@ -57,16 +55,11 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
} }
httpSession.Save(r, w) httpSession.Save(r, w)
// create a wide session
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"session": wideSession} "sid": session.WideSessions.GenId()}
wideSessions := session.WideSessions.GetByUsername(username) wideSessions := session.WideSessions.GetByUsername(username)

View File

@ -55,7 +55,7 @@ var notification = {
}; };
notificationWS.onerror = function (e) { notificationWS.onerror = function (e) {
console.log('[notification onerror] ' + JSON.parse(e)); console.log('[notification onerror]');
}; };
} }
}; };

View File

@ -186,7 +186,7 @@ var session = {
$(".notification-count").show(); $(".notification-count").show();
}; };
sessionWS.onerror = function (e) { sessionWS.onerror = function (e) {
console.log('[session onerror] ' + JSON.parse(e)); console.log('[session onerror]');
}; };
} }
}; };

View File

@ -540,7 +540,7 @@ var wide = {
console.log('[output onclose] disconnected (' + e.code + ')'); console.log('[output onclose] disconnected (' + e.code + ')');
}; };
outputWS.onerror = function (e) { outputWS.onerror = function (e) {
console.log('[output onerror] ' + e); console.log('[output onerror]');
}; };
}, },
_initFooter: function () { _initFooter: function () {
@ -812,8 +812,8 @@ $(document).ready(function () {
tree.init(); tree.init();
menu.init(); menu.init();
hotkeys.init(); hotkeys.init();
notification.init();
session.init(); session.init();
notification.init();
editors.init(); editors.init();
windows.init(); windows.init();
bottomGroup.init(); bottomGroup.init();

View File

@ -30,7 +30,7 @@
<link id="themesLink" rel="stylesheet" href="{{.conf.StaticServer}}/static/css/themes/{{.user.Theme}}.css?{{.conf.StaticResourceVersion}}"> <link id="themesLink" rel="stylesheet" href="{{.conf.StaticServer}}/static/css/themes/{{.user.Theme}}.css?{{.conf.StaticResourceVersion}}">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/user/{{.session.Username}}/style.css?{{.conf.StaticResourceVersion}}"> <link rel="stylesheet" href="{{.conf.StaticServer}}/static/user/{{.username}}/style.css?{{.conf.StaticResourceVersion}}">
<link rel="icon" type="image/x-icon" href="/favicon.ico" /> <link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head> </head>
@ -613,7 +613,7 @@
"pathSeparator": '{{.pathSeparator}}', "pathSeparator": '{{.pathSeparator}}',
"label": {{.i18n}}, "label": {{.i18n}},
"channel": {{.conf.Channel}}, "channel": {{.conf.Channel}},
"wideSessionId": '{{.session.ID}}', "wideSessionId": '{{.sid}}',
"editorTheme": '{{.user.Editor.Theme}}', "editorTheme": '{{.user.Editor.Theme}}',
"latestSessionContent": {{.latestSessionContent}}, "latestSessionContent": {{.latestSessionContent}},
"editorTabSize": '{{.user.Editor.TabSize}}', "editorTabSize": '{{.user.Editor.TabSize}}',

View File

@ -100,7 +100,7 @@
"server": "{{.conf.Server}}", "server": "{{.conf.Server}}",
"staticServer": "{{.conf.StaticServer}}", "staticServer": "{{.conf.StaticServer}}",
"channel": "{{.conf.Channel}}", "channel": "{{.conf.Channel}}",
"wideSessionId": "{{.session.ID}}", "wideSessionId": "{{.sid}}",
"label": {{.i18n}}, "label": {{.i18n}},
"autocomplete": {{.conf.Autocomplete}}, "autocomplete": {{.conf.Autocomplete}},
"embed": {{.embed}}, "embed": {{.embed}},

View File

@ -24,7 +24,7 @@
channel: { channel: {
shell: '{{.conf.ShellChannel}}' shell: '{{.conf.ShellChannel}}'
}, },
wideSessionId: {{.session.ID}} wideSessionId: {{.sid}}
};</script> };</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/jquery-2.1.1.min.js"></script> <script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/reconnecting-websocket.js"></script> <script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/reconnecting-websocket.js"></script>