// panicRecover wraps the panic recover process. func panicRecover(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { defer util.Recover() handler(w, r) } }
// New initializes a user event queue with the specified wide session id. func (ueqs queues) New(sid string) *UserEventQueue { if q, ok := ueqs[sid]; ok { logger.Warnf("Already exist a user queue in session [%s]", sid) return q } q := &UserEventQueue{ Sid: sid, Queue: make(chan *Event, maxQueueLength), } ueqs[sid] = q go func() { // start listening defer util.Recover() for evt := range q.Queue { logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code) // process event by each handlers for _, handler := range q.Handlers { handler.Handle(evt) } } }() return q }
//初始化配额配置 func InitQuotaAll() { defer util.Recover() rows, err := db.MySQL.Query(SELECT_QUOTA_ALL) if err != nil { logger.Errorf("load quota err [%s]", err) return } defer rows.Close() QuotaAll = nil QuotaAll = make(map[string]Quota) key := bytes.Buffer{} for rows.Next() { quota := Quota{} if err := rows.Scan("a.Id, "a.CustomerId, "a.TenantId, "a.ApiName, "a.Type, "a.Value, "a.Created, "a.Created); err != nil { logger.Errorf("load quota err [%s]", err) break } key.WriteString(quota.CustomerId) key.WriteString(quota.TenantId) key.WriteString(quota.ApiName) key.WriteString(quota.Type) QuotaAll[key.String()] = quota key.Reset() } if err = rows.Err(); err != nil { logger.Errorf("load quota err [%s]", err) } }
// FixedTimeSave saves online users' configurations periodically (1 minute). // // Main goal of this function is to save user session content, for restoring session content while user open Wide next time. func FixedTimeSave() { go func() { defer util.Recover() for _ = range time.Tick(time.Minute) { users := getOnlineUsers() for _, u := range users { if u.Save() { logger.Tracef("Saved online user [%s]'s configurations", u.Name) } } } }() }
// Load initializes the event handling. func Load() { go func() { defer util.Recover() for event := range EventQueue { logger.Debugf("Received a global event [code=%d]", event.Code) // dispatch the event to each user event queue for _, userQueue := range UserEventQueues { event.Sid = userQueue.Sid userQueue.Queue <- event } } }() }
// FixedTimeRelease releases invalid sessions. // // 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). // // Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field. func FixedTimeRelease() { go func() { defer util.Recover() for _ = range time.Tick(time.Hour) { hour, _ := time.ParseDuration("-30m") threshold := time.Now().Add(hour) for _, s := range WideSessions { if s.Updated.Before(threshold) { logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.Username) WideSessions.Remove(s.ID) } } } }() }
// FixedTimeReport reports the Wide sessions status periodically (10 minutes). func FixedTimeReport() { go func() { defer util.Recover() for _ = range time.Tick(10 * time.Minute) { users := userReports{} processSum := 0 for _, s := range WideSessions { processCnt := len(s.Processes) processSum += processCnt if report, exists := contains(users, s.Username); exists { if s.Updated.After(report.updated) { report.updated = s.Updated } report.sessionCnt++ report.processCnt += processCnt } else { users = append(users, &userReport{username: s.Username, sessionCnt: 1, processCnt: processCnt, updated: s.Updated}) } } var buf bytes.Buffer buf.WriteString("\n [" + strconv.Itoa(len(users)) + "] users, [" + strconv.Itoa(processSum) + "] running processes and [" + strconv.Itoa(len(WideSessions)) + "] sessions currently\n") sort.Sort(users) for _, t := range users { buf.WriteString(" " + t.report() + "\n") } logger.Info(buf.String()) } }() }
func checkEnv() { defer util.Recover() cmd := exec.Command("go", "version") buf, err := cmd.CombinedOutput() if nil != err { logger.Error("Not found 'go' command, please make sure Go has been installed correctly") os.Exit(-1) } logger.Trace(string(buf)) if "" == os.Getenv("GOPATH") { logger.Error("Not found $GOPATH, please configure it before running Wide") os.Exit(-1) } gocode := util.Go.GetExecutableInGOBIN("gocode") cmd = exec.Command(gocode) _, err = cmd.Output() if nil != err { event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound} logger.Warnf("Not found gocode [%s], please install it with this command: go get github.com/nsf/gocode", gocode) } ideStub := util.Go.GetExecutableInGOBIN("ide_stub") cmd = exec.Command(ideStub, "version") _, err = cmd.Output() if nil != err { event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound} logger.Warnf("Not found ide_stub [%s], please install it with this command: go get github.com/88250/ide_stub", ideStub) } }
// GoInstallHandler handles request of go install. func GoInstallHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) filePath := args["file"].(string) curDir := filepath.Dir(filePath) cmd := exec.Command("go", "install") cmd.Dir = curDir setCmdEnv(cmd, username) logger.Debugf("go install %s", curDir) stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [go install]" in front-end browser channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n" channelRet["cmd"] = "start-install" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Error(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() defer cmd.Wait() logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", username, sid, runningId, curDir) // read all buf, _ := ioutil.ReadAll(reader) channelRet := map[string]interface{}{} channelRet["cmd"] = "go install" if 0 != len(buf) { // build error // build gutter lint errOut := string(buf) lines := strings.Split(errOut, "\n") if lines[0][0] == '#' { lines = lines[1:] // skip the first line } lints := []*Lint{} for _, line := range lines { if len(line) < 1 { continue } if line[0] == '\t' { // append to the last lint last := len(lints) msg := lints[last-1].Msg msg += line lints[last-1].Msg = msg continue } file := line[:strings.Index(line, ":")] left := line[strings.Index(line, ":")+1:] index := strings.Index(left, ":") lineNo := 0 msg := left if index >= 0 { lineNo, _ = strconv.Atoi(left[:index]) msg = left[index+2:] } lint := &Lint{ File: file, LineNo: lineNo - 1, Severity: lintSeverityError, Msg: msg, } lints = append(lints, lint) } channelRet["lints"] = lints channelRet["output"] = "<span class='install-error'>" + i18n.Get(locale, "install-error").(string) + "</span>\n" + errOut } else { channelRet["output"] = "<span class='install-succ'>" + i18n.Get(locale, "install-succ").(string) + "</span>\n" } if nil != session.OutputWS[sid] { logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", username, sid, runningId, curDir) wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }
// BuildHandler handles request of building. func BuildHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) filePath := args["file"].(string) if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { http.Error(w, "Forbidden", http.StatusForbidden) return } curDir := filepath.Dir(filePath) fout, err := os.Create(filePath) if nil != err { logger.Error(err) result.Succ = false return } code := args["code"].(string) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false return } suffix := "" if util.OS.IsWindows() { suffix = ".exe" } cmd := exec.Command("go", "build") cmd.Dir = curDir setCmdEnv(cmd, username) executable := filepath.Base(curDir) + suffix executable = filepath.Join(curDir, executable) stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [go build]" in front-end browser channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n" channelRet["cmd"] = "start-build" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Error(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() defer cmd.Wait() // logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir) // read all buf, _ := ioutil.ReadAll(reader) channelRet := map[string]interface{}{} channelRet["cmd"] = "build" channelRet["executable"] = executable if 0 == len(buf) { // build success channelRet["nextCmd"] = args["nextCmd"] channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n" go func() { // go install, for subsequent gocode lib-path defer util.Recover() cmd := exec.Command("go", "install") cmd.Dir = curDir setCmdEnv(cmd, username) out, _ := cmd.CombinedOutput() if len(out) > 0 { logger.Warn(string(out)) } }() } else { // build error // build gutter lint errOut := string(buf) lines := strings.Split(errOut, "\n") // path process var errOutWithPath string for _, line := range lines { errOutWithPath += parsePath(curDir, line) + "\n" } channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" + "<span class='stderr'>" + errOutWithPath + "</span>" // lint process if lines[0][0] == '#' { lines = lines[1:] // skip the first line } lints := []*Lint{} for _, line := range lines { if len(line) < 1 { continue } if line[0] == '\t' { // append to the last lint last := len(lints) msg := lints[last-1].Msg msg += line lints[last-1].Msg = msg continue } file := line[:strings.Index(line, ":")] left := line[strings.Index(line, ":")+1:] index := strings.Index(left, ":") lineNo := 0 msg := left if index >= 0 { lineNo, err = strconv.Atoi(left[:index]) if nil != err { continue } msg = left[index+2:] } lint := &Lint{ File: filepath.Join(curDir, file), LineNo: lineNo - 1, Severity: lintSeverityError, Msg: msg, } lints = append(lints, lint) } channelRet["lints"] = lints } if nil != session.OutputWS[sid] { // logger.Debugf("User [%s, %s] 's build [id=%d, dir=%s] has done", username, sid, runningId, curDir) wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }
// RunHandler handles request of executing a binary file. func RunHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false } sid := args["sid"].(string) wSession := session.WideSessions.Get(sid) if nil == wSession { result.Succ = false } filePath := args["executable"].(string) cmd := exec.Command(filePath) if conf.Docker { output.SetNamespace(cmd) } stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false } outReader := bufio.NewReader(stdout) errReader := bufio.NewReader(stderr) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false } wsChannel := session.PlaygroundWS[sid] channelRet := map[string]interface{}{} if !result.Succ { if nil != wsChannel { channelRet["cmd"] = "run-done" channelRet["output"] = "" err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } return } channelRet["pid"] = cmd.Process.Pid // add the process to user's process set output.Processes.Add(wSession, cmd.Process) go func(runningId int) { defer util.Recover() defer cmd.Wait() logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath) // push once for front-end to get the 'run' state and pid if nil != wsChannel { channelRet["cmd"] = "run" channelRet["output"] = "" err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } go func() { defer util.Recover() buf := outputBuf{} for { wsChannel := session.PlaygroundWS[sid] if nil == wsChannel { break } r, _, err := outReader.ReadRune() if nil != err { // remove the exited process from user process set output.Processes.Remove(wSession, cmd.Process) logger.Debugf("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err) channelRet["cmd"] = "run-done" channelRet["output"] = buf.content err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) break } wsChannel.Refresh() break } oneRuneStr := string(r) buf.content += oneRuneStr now := time.Now().UnixNano() / int64(time.Millisecond) if 0 == buf.millisecond { buf.millisecond = now } if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" { channelRet["cmd"] = "run" channelRet["output"] = buf.content buf = outputBuf{} // a new buffer err = wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) break } wsChannel.Refresh() } } }() buf := outputBuf{} for { r, _, err := errReader.ReadRune() wsChannel := session.PlaygroundWS[sid] if nil != err || nil == wsChannel { break } oneRuneStr := string(r) buf.content += oneRuneStr now := time.Now().UnixNano() / int64(time.Millisecond) if 0 == buf.millisecond { buf.millisecond = now } if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" { channelRet["cmd"] = "run" channelRet["output"] = buf.content buf = outputBuf{} // a new buffer err = wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) break } wsChannel.Refresh() } } }(rand.Int()) }
// 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() 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("Added a file watcher [%s]", dirPath) } return nil }) } ret.FileWatcher = watcher }() 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 } if event.Op&fsnotify.Create == fsnotify.Create { if err = watcher.Add(path); nil != err { logger.Warn(err, path) } logger.Tracef("Added a file watcher [%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) } } } }() return ret }
// GoVetHandler handles request of go vet. func GoVetHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) filePath := args["file"].(string) curDir := filepath.Dir(filePath) cmd := exec.Command("go", "vet", ".") cmd.Dir = curDir setCmdEnv(cmd, username) stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [go vet]" in front-end browser channelRet["output"] = "<span class='start-vet'>" + i18n.Get(locale, "start-vet").(string) + "</span>\n" channelRet["cmd"] = "start-vet" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", username, sid, runningId) channelRet := map[string]interface{}{} channelRet["cmd"] = "go vet" // read all buf, _ := ioutil.ReadAll(reader) // waiting for go vet finished cmd.Wait() if !cmd.ProcessState.Success() { logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", username, sid, runningId) channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf) } else { logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", username, sid, runningId) channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf) } if nil != session.OutputWS[sid] { wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }
// Clone handles request of git clone. func CloneHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) path := args["path"].(string) repository := args["repository"].(string) cmd := exec.Command("git", "clone", repository) cmd.Dir = path stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [git clone]" in front-end browser channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-git_clone").(string) + "</span>\n" channelRet["cmd"] = "start-git_clone" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() defer cmd.Wait() logger.Debugf("User [%s, %s] is running [git clone] [runningId=%d]", username, sid, runningId) channelRet := map[string]interface{}{} channelRet["cmd"] = "git clone" // read all buf, err := ioutil.ReadAll(reader) if nil != err { logger.Warn(err) // TODO: handle clone error } logger.Debugf("User [%s, %s] 's running [git clone] [runningId=%d] has done: %s", username, sid, runningId, string(buf)) channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "git_clone-done").(string) + "</span>\n" if nil != session.OutputWS[sid] { wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }