This commit is contained in:
parent
7521c8b6d4
commit
36a8bb4d50
47
conf/wide.go
47
conf/wide.go
|
@ -20,6 +20,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -42,6 +43,15 @@ const (
|
|||
WideVersion = "1.1.0"
|
||||
// CodeMirrorVer holds the current editor version.
|
||||
CodeMirrorVer = "4.10"
|
||||
|
||||
HelloWorld = `package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, 世界")
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// Configuration.
|
||||
|
@ -59,6 +69,7 @@ type conf struct {
|
|||
RuntimeMode string // runtime mode (dev/prod)
|
||||
WD string // current working direcitory, ${pwd}
|
||||
Locale string // default locale
|
||||
Playground string // playground directory
|
||||
}
|
||||
|
||||
// Logger.
|
||||
|
@ -74,8 +85,12 @@ var Users []*User
|
|||
var Docker bool
|
||||
|
||||
// Load loads the Wide configurations from wide.json and users' configurations from users/{username}.json.
|
||||
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel string, confDocker bool) {
|
||||
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, confDocker)
|
||||
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
||||
confPlayground string, confDocker bool) {
|
||||
// XXX: ugly args list....
|
||||
|
||||
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
||||
confPlayground, confDocker)
|
||||
initUsers()
|
||||
}
|
||||
|
||||
|
@ -114,7 +129,8 @@ func initUsers() {
|
|||
initCustomizedConfs()
|
||||
}
|
||||
|
||||
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel string, confDocker bool) {
|
||||
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
||||
confPlayground string, confDocker bool) {
|
||||
bytes, err := ioutil.ReadFile(confPath)
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
|
@ -144,6 +160,31 @@ func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticSe
|
|||
Wide.WD = util.OS.Pwd()
|
||||
logger.Debugf("${pwd} [%s]", Wide.WD)
|
||||
|
||||
// User Home
|
||||
user, err := user.Current()
|
||||
if nil != err {
|
||||
logger.Error("Can't get user's home, please report this issue to developer")
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
userHome := user.HomeDir
|
||||
logger.Debugf("${user.home} [%s]", userHome)
|
||||
|
||||
// Playground Directory
|
||||
Wide.Playground = strings.Replace(Wide.Playground, "${home}", userHome, 1)
|
||||
if "" != confPlayground {
|
||||
Wide.Playground = confPlayground
|
||||
}
|
||||
|
||||
if !util.File.IsExist(Wide.Playground) {
|
||||
if err := os.Mkdir(Wide.Playground, 0775); nil != err {
|
||||
logger.Errorf("Create Playground [%s] error", err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// IP
|
||||
ip, err := util.Net.LocalIP()
|
||||
if err != nil {
|
||||
|
|
|
@ -11,5 +11,6 @@
|
|||
"MaxProcs": 4,
|
||||
"RuntimeMode": "dev",
|
||||
"WD": "${pwd}",
|
||||
"Locale": "en_US"
|
||||
"Locale": "en_US",
|
||||
"Playground": "${home}/playground"
|
||||
}
|
26
main.go
26
main.go
|
@ -37,8 +37,8 @@ import (
|
|||
"github.com/b3log/wide/log"
|
||||
"github.com/b3log/wide/notification"
|
||||
"github.com/b3log/wide/output"
|
||||
"github.com/b3log/wide/playground"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/shell"
|
||||
"github.com/b3log/wide/util"
|
||||
)
|
||||
|
||||
|
@ -57,6 +57,7 @@ func init() {
|
|||
confChannel := flag.String("channel", "", "this will overwrite Wide.Channel if specified")
|
||||
confStat := flag.Bool("stat", false, "whether report statistics periodically")
|
||||
confDocker := flag.Bool("docker", false, "whether run in a docker container")
|
||||
confPlayground := flag.String("playground", "", "this will overwrite Wide.Playground if specified")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
@ -75,7 +76,7 @@ func init() {
|
|||
event.Load()
|
||||
|
||||
conf.Load(*confPath, *confIP, *confPort, *confServer, *confLogLevel, *confStaticServer, *confContext, *confChannel,
|
||||
*confDocker)
|
||||
*confPlayground, *confDocker)
|
||||
|
||||
conf.FixedTimeCheckEnv()
|
||||
|
||||
|
@ -151,8 +152,8 @@ func main() {
|
|||
http.HandleFunc(conf.Wide.Context+"/find/usages", handlerWrapper(editor.FindUsagesHandler))
|
||||
|
||||
// shell
|
||||
http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
|
||||
http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
|
||||
// http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
|
||||
// http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
|
||||
|
||||
// notification
|
||||
http.HandleFunc(conf.Wide.Context+"/notification/ws", handlerWrapper(notification.WSHandler))
|
||||
|
@ -163,6 +164,14 @@ func main() {
|
|||
http.HandleFunc(conf.Wide.Context+"/signup", handlerWrapper(session.SignUpUser))
|
||||
http.HandleFunc(conf.Wide.Context+"/preference", handlerWrapper(session.PreferenceHandler))
|
||||
|
||||
// playground
|
||||
http.HandleFunc(conf.Wide.Context+"/playground", handlerWrapper(playground.IndexHandler))
|
||||
http.HandleFunc(conf.Wide.Context+"/playground/ws", handlerWrapper(playground.WSHandler))
|
||||
http.HandleFunc(conf.Wide.Context+"/playground/save", handlerWrapper(playground.SaveHandler))
|
||||
http.HandleFunc(conf.Wide.Context+"/playground/build", handlerWrapper(playground.BuildHandler))
|
||||
http.HandleFunc(conf.Wide.Context+"/playground/run", handlerWrapper(playground.RunHandler))
|
||||
http.HandleFunc(conf.Wide.Context+"/playground/stop", handlerWrapper(playground.StopHandler))
|
||||
|
||||
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context)
|
||||
|
||||
err := http.ListenAndServe(conf.Wide.Server, nil)
|
||||
|
@ -186,6 +195,14 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
username := httpSession.Values["username"].(string)
|
||||
|
||||
if "playground" == username { // reserved user for Playground
|
||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
if "" != conf.Wide.Context {
|
||||
httpSession.Options.Path = conf.Wide.Context
|
||||
|
@ -197,7 +214,6 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sid := strconv.Itoa(rand.Int())
|
||||
wideSession := session.WideSessions.New(httpSession, sid)
|
||||
|
||||
username := httpSession.Values["username"].(string)
|
||||
user := conf.GetUser(username)
|
||||
if nil == user {
|
||||
logger.Warnf("Not found user [%s]", username)
|
||||
|
|
|
@ -20,6 +20,6 @@ import (
|
|||
"os/exec"
|
||||
)
|
||||
|
||||
func setNamespace(cmd *exec.Cmd) {
|
||||
func SetNamespace(cmd *exec.Cmd) {
|
||||
// do nothing
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
func setNamespace(cmd *exec.Cmd) {
|
||||
func SetNamespace(cmd *exec.Cmd) {
|
||||
// XXX: keep move with Go 1.4 and later's
|
||||
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
|
|
|
@ -27,13 +27,13 @@ type procs map[string][]*os.Process
|
|||
// Processse of all users.
|
||||
//
|
||||
// <sid, []*os.Process>
|
||||
var processes = procs{}
|
||||
var Processes = procs{}
|
||||
|
||||
// Exclusive lock.
|
||||
var mutex sync.Mutex
|
||||
|
||||
// add adds the specified process to the user process set.
|
||||
func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
|
||||
func (procs *procs) Add(wSession *session.WideSession, proc *os.Process) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
|
@ -50,7 +50,7 @@ func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
|
|||
}
|
||||
|
||||
// remove removes the specified process from the user process set.
|
||||
func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
|
||||
func (procs *procs) Remove(wSession *session.WideSession, proc *os.Process) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
|
@ -75,7 +75,7 @@ func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
|
|||
}
|
||||
|
||||
// kill kills a process specified by the given pid.
|
||||
func (procs *procs) kill(wSession *session.WideSession, pid int) {
|
||||
func (procs *procs) Kill(wSession *session.WideSession, pid int) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
|
|||
cmd.Dir = curDir
|
||||
|
||||
if conf.Docker {
|
||||
setNamespace(cmd)
|
||||
SetNamespace(cmd)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
|
@ -111,7 +111,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
|
|||
channelRet["pid"] = cmd.Process.Pid
|
||||
|
||||
// add the process to user's process set
|
||||
processes.add(wSession, cmd.Process)
|
||||
Processes.Add(wSession, cmd.Process)
|
||||
|
||||
go func(runningId int) {
|
||||
defer util.Recover()
|
||||
|
@ -151,7 +151,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if nil != err {
|
||||
// remove the exited process from user process set
|
||||
processes.remove(wSession, cmd.Process)
|
||||
Processes.Remove(wSession, cmd.Process)
|
||||
|
||||
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
|
||||
|
||||
|
@ -253,5 +253,5 @@ func StopHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
processes.kill(wSession, pid)
|
||||
Processes.Kill(wSession, pid)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) 2014-2015, b3log.org
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// 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.
|
||||
|
||||
package playground
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/b3log/wide/conf"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/util"
|
||||
)
|
||||
|
||||
// BuildHandler handles request of Playground building.
|
||||
func BuildHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
if httpSession.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
filePath := args["filePath"].(string)
|
||||
|
||||
suffix := ""
|
||||
if util.OS.IsWindows() {
|
||||
suffix = ".exe"
|
||||
}
|
||||
|
||||
fileName := filepath.Base(filePath)
|
||||
executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1))
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", executable, filePath)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
data["output"] = template.HTML(string(out))
|
||||
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data["executable"] = executable
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright (c) 2014-2015, b3log.org
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// 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.
|
||||
|
||||
package playground
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/b3log/wide/conf"
|
||||
)
|
||||
|
||||
// SaveHandler handles request of Playground code save.
|
||||
func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
if session.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
|
||||
// generate file name
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(code))
|
||||
fileName := hex.EncodeToString(hasher.Sum(nil))
|
||||
fileName += ".go"
|
||||
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName)
|
||||
|
||||
fout, err := os.Create(filePath)
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data["filePath"] = filePath
|
||||
data["url"] = filepath.ToSlash(filePath)
|
||||
|
||||
argv := []string{filePath}
|
||||
cmd := exec.Command("gofmt", argv...)
|
||||
|
||||
bytes, _ := cmd.Output()
|
||||
output := string(bytes)
|
||||
if "" == output {
|
||||
// format error, returns the original content
|
||||
data["succ"] = true
|
||||
data["code"] = code
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code = string(output)
|
||||
data["code"] = code
|
||||
|
||||
// generate file name
|
||||
hasher = md5.New()
|
||||
hasher.Write([]byte(code))
|
||||
fileName = hex.EncodeToString(hasher.Sum(nil))
|
||||
fileName += ".go"
|
||||
filePath = filepath.Clean(conf.Wide.Playground + "/" + fileName)
|
||||
data["filePath"] = filePath
|
||||
data["url"] = filepath.ToSlash(filePath)
|
||||
|
||||
fout, err = os.Create(filePath)
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) 2014-2015, b3log.org
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Package shell include playground related mainipulations.
|
||||
package playground
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/b3log/wide/conf"
|
||||
"github.com/b3log/wide/i18n"
|
||||
"github.com/b3log/wide/log"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Logger.
|
||||
var logger = log.NewLogger(os.Stdout)
|
||||
|
||||
// IndexHandler handles request of Playground index.
|
||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
username := "playground"
|
||||
|
||||
// create a HTTP session
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
httpSession.Values["username"] = username
|
||||
if httpSession.IsNew {
|
||||
httpSession.Values["id"] = strconv.Itoa(rand.Int())
|
||||
}
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
if "" != conf.Wide.Context {
|
||||
httpSession.Options.Path = conf.Wide.Context
|
||||
}
|
||||
httpSession.Save(r, w)
|
||||
|
||||
// create a wide session
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
sid := strconv.Itoa(rand.Int())
|
||||
wideSession := session.WideSessions.New(httpSession, sid)
|
||||
|
||||
locale := conf.Wide.Locale
|
||||
|
||||
code := conf.HelloWorld
|
||||
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||
"session": wideSession, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
|
||||
"code": template.HTML(code)}
|
||||
|
||||
wideSessions := session.WideSessions.GetByUsername(username)
|
||||
|
||||
logger.Tracef("User [%s] has [%d] sessions", username, len(wideSessions))
|
||||
|
||||
t, err := template.ParseFiles("views/playground/index.html")
|
||||
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Execute(w, model)
|
||||
}
|
||||
|
||||
// WSHandler handles request of creating Playground channel.
|
||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sid := r.URL.Query()["sid"][0]
|
||||
|
||||
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
|
||||
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
|
||||
|
||||
ret := map[string]interface{}{"output": "Playground initialized", "cmd": "init-playground"}
|
||||
err := wsChan.WriteJSON(&ret)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
session.PlaygroundWS[sid] = &wsChan
|
||||
|
||||
logger.Tracef("Open a new [PlaygroundWS] with session [%s], %d", sid, len(session.PlaygroundWS))
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
// Copyright (c) 2014-2015, b3log.org
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// 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.
|
||||
|
||||
package playground
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/b3log/wide/conf"
|
||||
"github.com/b3log/wide/output"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/util"
|
||||
)
|
||||
|
||||
const (
|
||||
outputBufMax = 128 // 128 string(rune)
|
||||
outputTimeout = 100 // 100ms
|
||||
)
|
||||
|
||||
type outputBuf struct {
|
||||
content string
|
||||
millisecond int64
|
||||
}
|
||||
|
||||
// RunHandler handles request of executing a binary file.
|
||||
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
}
|
||||
|
||||
sid := args["sid"].(string)
|
||||
wSession := session.WideSessions.Get(sid)
|
||||
if nil == wSession {
|
||||
data["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)
|
||||
data["succ"] = false
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
}
|
||||
|
||||
outReader := bufio.NewReader(stdout)
|
||||
errReader := bufio.NewReader(stderr)
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
}
|
||||
|
||||
wsChannel := session.PlaygroundWS[sid]
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
|
||||
if !data["succ"].(bool) {
|
||||
if nil != wsChannel {
|
||||
channelRet["cmd"] = "run-done"
|
||||
channelRet["output"] = ""
|
||||
|
||||
err := wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Error(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()
|
||||
|
||||
// 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.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
|
||||
go func() {
|
||||
buf := outputBuf{}
|
||||
|
||||
for {
|
||||
wsChannel := session.PlaygroundWS[sid]
|
||||
if nil == wsChannel {
|
||||
break
|
||||
}
|
||||
|
||||
r, _, err := outReader.ReadRune()
|
||||
|
||||
oneRuneStr := string(r)
|
||||
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
||||
|
||||
buf.content += oneRuneStr
|
||||
|
||||
if nil != err {
|
||||
// remove the exited process from user process set
|
||||
output.Processes.Remove(wSession, cmd.Process)
|
||||
|
||||
logger.Tracef("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.Error(err)
|
||||
break
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
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.Error(err)
|
||||
break
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
buf := outputBuf{}
|
||||
for {
|
||||
r, _, err := errReader.ReadRune()
|
||||
|
||||
wsChannel := session.PlaygroundWS[sid]
|
||||
if nil != err || nil == wsChannel {
|
||||
break
|
||||
}
|
||||
|
||||
oneRuneStr := string(r)
|
||||
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
||||
|
||||
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"] = "<span class='stderr'>" + buf.content + "</span>"
|
||||
|
||||
buf = outputBuf{} // a new buffer
|
||||
|
||||
err = wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
break
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}
|
||||
}(rand.Int())
|
||||
}
|
||||
|
||||
// StopHandler handles request of stoping a running process.
|
||||
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sid := args["sid"].(string)
|
||||
pid := int(args["pid"].(float64))
|
||||
|
||||
wSession := session.WideSessions.Get(sid)
|
||||
if nil == wSession {
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
output.Processes.Kill(wSession, pid)
|
||||
}
|
|
@ -60,6 +60,9 @@ var (
|
|||
|
||||
// NotificationWS holds all notification channels. <sid, *util.WSChannel>
|
||||
NotificationWS = map[string]*util.WSChannel{}
|
||||
|
||||
// PlaygroundWS holds all playground channels. <sid, *util.WSChannel>
|
||||
PlaygroundWS = map[string]*util.WSChannel{}
|
||||
)
|
||||
|
||||
// HTTP session store.
|
||||
|
@ -371,6 +374,11 @@ func (sessions *wSessions) Remove(sid string) {
|
|||
delete(SessionWS, sid)
|
||||
}
|
||||
|
||||
if ws, ok := PlaygroundWS[sid]; ok {
|
||||
ws.Close()
|
||||
delete(PlaygroundWS, sid)
|
||||
}
|
||||
|
||||
cnt := 0 // count wide sessions associated with HTTP session
|
||||
for _, ses := range *sessions {
|
||||
if ses.HTTPSession.Values["id"] == s.HTTPSession.Values["id"] {
|
||||
|
|
|
@ -315,6 +315,10 @@ func getOnlineUsers() []*conf.User {
|
|||
for _, username := range usernames {
|
||||
u := conf.GetUser(username)
|
||||
|
||||
if "playground" == username { // user [playground] is a reserved mock user
|
||||
continue
|
||||
}
|
||||
|
||||
if nil == u {
|
||||
logger.Warnf("Not found user [%s]", username)
|
||||
|
||||
|
@ -333,7 +337,13 @@ func getOnlineUsers() []*conf.User {
|
|||
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a http version)
|
||||
// 3. update the user customized configurations, such as style.css
|
||||
// 4. serve files of the user's workspace via HTTP
|
||||
//
|
||||
// Note: user [playground] is a reserved mock user
|
||||
func addUser(username, password, email string) string {
|
||||
if "playground" == username {
|
||||
return userExists
|
||||
}
|
||||
|
||||
addUserMutex.Lock()
|
||||
defer addUserMutex.Unlock()
|
||||
|
||||
|
@ -393,14 +403,7 @@ func consoleHello(workspace string) {
|
|||
return
|
||||
}
|
||||
|
||||
fout.WriteString(`package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, 世界")
|
||||
}
|
||||
`)
|
||||
fout.WriteString(conf.HelloWorld)
|
||||
|
||||
fout.Close()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright (c) 2014-2015, b3log.org
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
var playground = {
|
||||
editor: undefined,
|
||||
pid: undefined,
|
||||
init: function () {
|
||||
$("#editorDiv").append("<textarea id='editor'></textarea>");
|
||||
var textArea = document.getElementById("editor");
|
||||
textArea.value = code;
|
||||
playground.editor = CodeMirror.fromTextArea(textArea, {
|
||||
lineNumbers: true,
|
||||
autofocus: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
highlightSelectionMatches: {showToken: /\w/},
|
||||
rulers: [{color: "#ccc", column: 120, lineStyle: "dashed"}],
|
||||
styleActiveLine: true,
|
||||
theme: "wide",
|
||||
tabSize: 4,
|
||||
indentUnit: 4,
|
||||
foldGutter: true,
|
||||
cursorHeight: 1,
|
||||
});
|
||||
|
||||
this._initWS();
|
||||
},
|
||||
_initWS: function () {
|
||||
// Used for session retention, server will release all resources of the session if this channel closed
|
||||
var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);
|
||||
|
||||
sessionWS.onopen = function () {
|
||||
console.log('[session onopen] connected');
|
||||
};
|
||||
|
||||
sessionWS.onmessage = function (e) {
|
||||
console.log('[session onmessage]' + e.data);
|
||||
};
|
||||
sessionWS.onclose = function (e) {
|
||||
console.log('[session onclose] disconnected (' + e.code + ')');
|
||||
};
|
||||
sessionWS.onerror = function (e) {
|
||||
console.log('[session onerror] ' + JSON.parse(e));
|
||||
};
|
||||
|
||||
var playgroundWS = new ReconnectingWebSocket(config.channel + '/playground/ws?sid=' + config.wideSessionId);
|
||||
|
||||
playgroundWS.onopen = function () {
|
||||
console.log('[playground onopen] connected');
|
||||
};
|
||||
|
||||
playgroundWS.onmessage = function (e) {
|
||||
console.log('[playground onmessage]' + e.data);
|
||||
|
||||
var data = JSON.parse(e.data);
|
||||
|
||||
playground.pid = data.pid;
|
||||
|
||||
var val = $("#output").val();
|
||||
$("#output").val(val + data.output);
|
||||
};
|
||||
playgroundWS.onclose = function (e) {
|
||||
console.log('[playground onclose] disconnected (' + e.code + ')');
|
||||
};
|
||||
playgroundWS.onerror = function (e) {
|
||||
console.log('[playground onerror] ' + JSON.parse(e));
|
||||
};
|
||||
},
|
||||
share: function () {
|
||||
if (!playground.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = newWideRequest();
|
||||
request.pid = playground.pid;
|
||||
|
||||
var code = playground.editor.getValue();
|
||||
|
||||
var request = newWideRequest();
|
||||
request.code = code;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: config.context + '/playground/save',
|
||||
data: JSON.stringify(request),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
playground.editor.setValue(data.code);
|
||||
|
||||
if (!data.succ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = window.location.protocol + "//" + window.location.host + '/' + data.url;
|
||||
var html = '<a href="' + url + '" target="_blank">'
|
||||
+ url + "</a>";
|
||||
$("#url").html(html);
|
||||
}
|
||||
});
|
||||
},
|
||||
stop: function () {
|
||||
if (!playground.editor || !playground.pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = newWideRequest();
|
||||
request.pid = playground.pid;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: config.context + '/playground/stop',
|
||||
data: JSON.stringify(request),
|
||||
dataType: "json"
|
||||
});
|
||||
},
|
||||
run: function () {
|
||||
if (!playground.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var code = playground.editor.getValue();
|
||||
|
||||
// Step 1. save & format code
|
||||
var request = newWideRequest();
|
||||
request.code = code;
|
||||
|
||||
$("#output").val("");
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: config.context + '/playground/save',
|
||||
data: JSON.stringify(request),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
// console.log(data);
|
||||
playground.editor.setValue(data.code);
|
||||
|
||||
if (!data.succ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2. compile code
|
||||
var request = newWideRequest();
|
||||
request.filePath = data.filePath;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: config.context + '/playground/build',
|
||||
data: JSON.stringify(request),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
// console.log(data);
|
||||
|
||||
$("#output").val(data.output);
|
||||
|
||||
if (!data.succ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3. run the executable binary and handle its output
|
||||
var request = newWideRequest();
|
||||
request.executable = data.executable;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: config.context + '/playground/run',
|
||||
data: JSON.stringify(request),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
// console.log(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
playground.init();
|
||||
});
|
||||
|
|
@ -121,7 +121,7 @@ var session = {
|
|||
}
|
||||
},
|
||||
_initWS: function () {
|
||||
// 用于保持会话,如果该通道断开,则服务器端会销毁会话状态,回收相关资源.
|
||||
// Used for session retention, server will release all resources of the session if this channel closed
|
||||
var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);
|
||||
|
||||
sessionWS.onopen = function () {
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{.i18n.wide}} - {{.i18n.wide_title}}</title>
|
||||
|
||||
<meta name="keywords" content="Wide, Golang, IDE, Team, Cloud, B3log, Playground"/>
|
||||
<meta name="description" content="A Web-based IDE for Teams using Golang, do your development anytime, anywhere."/>
|
||||
<meta name="author" content="B3log">
|
||||
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/base.css?{{.conf.StaticResourceVersion}}">
|
||||
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/codemirror.css">
|
||||
<link rel="stylesheet" href="{{$.conf.StaticServer}}/static/js/overwrite/codemirror/theme/wide.css">
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<button id="run" onclick="playground.run();">Run</button>
|
||||
<button id="stop" onclick="playground.stop();">Stop</button>
|
||||
<button id="share" onclick="playground.share();">Share</button>
|
||||
<span id="url"></span>
|
||||
</div>
|
||||
<div>
|
||||
<div id="editorDiv">
|
||||
</div>
|
||||
<textarea id="output" rows="20" readonly="readonly" style="width: 100%;" ></textarea>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var config = {
|
||||
"context": "{{.conf.Context}}",
|
||||
"staticServer": "{{.conf.StaticServer}}",
|
||||
"channel": "{{.conf.Channel}}",
|
||||
"wideSessionId": "{{.session.ID}}"
|
||||
};
|
||||
var code = "{{.code}}";
|
||||
|
||||
function newWideRequest() {
|
||||
var ret = {
|
||||
sid: config.wideSessionId
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
</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/ztree/jquery.ztree.all-3.5.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/codemirror.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/selection/active-line.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/overwrite/codemirror/addon/hint/show-hint.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/hint/anyword-hint.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/display/rulers.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/edit/closebrackets.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/edit/matchbrackets.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/edit/closetag.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/search/searchcursor.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/search/search.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/dialog/dialog.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/search/match-highlighter.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/foldcode.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/foldgutter.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/brace-fold.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/comment-fold.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/mode/loadmode.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/comment/comment.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/mode/meta.js"></script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/mode/go/go.js"></script>
|
||||
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/playground.js?{{.conf.StaticResourceVersion}}"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue