Browse Source

init

master
public_host 5 years ago
parent
commit
a787788360
  1. 6
      .gitignore
  2. 3
      README.md
  3. 21
      client/govue.js
  4. 115
      cmd/main.go
  5. 327
      govue/bindata.go
  6. 34
      govue/cache.go
  7. 57
      govue/config.go
  8. 1
      govue/govue-js-src/.gitignore
  9. 8
      govue/govue-js-src/build.js
  10. 16
      govue/govue-js-src/govue.js
  11. 1424
      govue/govue-js-src/package-lock.json
  12. 17
      govue/govue-js-src/package.json
  13. 40550
      govue/govue-runtime/govue.js
  14. 178
      govue/govue-runtime/header.js
  15. 7203
      govue/govue-runtime/polyfill.js
  16. 60
      govue/govue-runtime/runtime.js
  17. 211
      govue/govue.go
  18. 58
      govue/utils.go
  19. 11
      jsruntime/console.go
  20. 9
      jsruntime/cookie.go
  21. 146
      jsruntime/go_request.go
  22. 12
      jsruntime/interval.go
  23. 291
      jsruntime/runtime.go
  24. 28
      jsruntime/timeout.go
  25. 120
      pool/pool.go
  26. 95
      static/example1/index.html
  27. 100
      static/index.html
  28. 20
      static/js/govue.js
  29. 11965
      static/js/vue.js

6
.gitignore

@ -1,4 +1,3 @@
# ---> Go
# Binaries for programs and plugins
*.exe
*.exe~
@ -6,9 +5,12 @@
*.so
*.dylib
# Test binary, build with `go test -c`
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
.idea

3
README.md

@ -1,2 +1 @@
# govue2
# go-vue

21
client/govue.js

@ -0,0 +1,21 @@
window.isBrowser = true;
function GoVueMount(app, ctx, el) {
var sourceTemplate = document.getElementsByTagName("go-vue-template");
var GoVueHTML = "";
if (sourceTemplate.length > 0) {
GoVueHTML = decodeURIComponent(atob(sourceTemplate[0].innerHTML))
}
var sourceData = document.getElementsByTagName("go-vue-data");
var GoVueData = {};
if (sourceData.length > 0) {
GoVueData = JSON.parse(decodeURIComponent(atob(sourceData[0].innerHTML)))
}
app.$options.template = GoVueHTML;
app.$mount(el);
for (var i in GoVueData) {
app[i] = GoVueData[i];
}
}

115
cmd/main.go

@ -0,0 +1,115 @@
package main
import (
"flag"
"fmt"
"git.ouxuan.net/3136352472/govue2/govue"
"github.com/gin-gonic/gin"
"log"
"mime"
"path/filepath"
)
func main() {
var addr string
var static string
var useFile string
var mode string
var path string
var errorPage404 string
flag.StringVar(&path, "config", "govue.json", "配置文件路径")
flag.StringVar(&static, "static", "", "静态文件目录")
flag.StringVar(&addr, "addr", "", "监听ip:port")
flag.StringVar(&mode, "mode", "", "模式release/debug")
flag.StringVar(&errorPage404, "error_page_404", "", "404错误页面路径")
flag.Parse()
if flag.Arg(0) == "init" {
err := govue.GenerateConfig(path)
if err != nil {
fmt.Print("文件:", path, "\n生成失败:", err)
} else {
abs, _ := filepath.Abs(path)
fmt.Print("文件:", abs, "\n生成成功:")
}
return
}
config, err := govue.GetConfig(path)
fmt.Println("配置载入:", path)
fmt.Println("渲染协程池最大总数:", config.Pool.MaxTotal)
fmt.Println("渲染协程池最大空闲数:", config.Pool.MaxIdle)
fmt.Println("渲染协程池最小空闲数:", config.Pool.MinIdle)
if addr != "" {
config.Addr = addr
}
if static != "" {
config.StaticDir = static
}
if useFile != "" {
config.UseJsFile = useFile
}
if mode != "" {
config.Mode = mode
}
gin.SetMode(config.Mode)
r := gin.Default()
govue.SetPoolConfig(config.Pool)
gv, err := govue.NewGoVue(config.UseJsFile, config.StaticDir, config.Mode == "debug")
if errorPage404 != "" {
config.ErrorPage404 = errorPage404
}
gv.SetErrorPage404(config.ErrorPage404)
gv.SetCacheSec(5)
if err != nil {
panic(err)
}
r.NoRoute(func(context *gin.Context) {
raw, err := gv.LoadStaticResources(context.Request)
if err != nil {
_, _ = context.Writer.Write(raw)
return
}
mime.TypeByExtension(filepath.Ext(context.Request.URL.Path))
ext := filepath.Ext(context.Request.URL.Path)
if ext == "" {
ext = ".html"
}
context.Writer.Header().Set("Content-Type", mime.TypeByExtension(ext)+"; charset=utf-8")
context.Writer.WriteHeader(200)
_, _ = context.Writer.Write(raw)
})
r.Use(func(context *gin.Context) {
context.Header("Access-Control-Allow-Origin", "*")
context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
context.Header("Access-Control-Allow-Headers", "Action, Module, X-PINGOTHER, Content-Type, Content-Disposition")
})
r.GET("/govue.version", func(context *gin.Context) {
context.JSON(200, map[string]interface{}{
"version": "0.9.1",
})
})
gv.StartPoolLog()
for {
err = r.Run(config.Addr)
if err != nil {
log.Println("服务意外停止:", err)
}
}
}

327
govue/bindata.go
File diff suppressed because it is too large
View File

34
govue/cache.go

@ -0,0 +1,34 @@
package govue
import (
"sync"
"time"
)
var cache sync.Map
type mcache struct {
html string
creation int64
timeoutSec int64
}
func GetCache(key string) string {
v, _ := cache.Load(key)
if v != nil {
mc := v.(*mcache)
if time.Now().Unix() < mc.creation+mc.timeoutSec {
//log.Println("加载缓存")
return mc.html
}
}
return ""
}
func SetCache(key string, val string, timeoutSec int64) {
cache.Store(key, &mcache{
html: val,
creation: time.Now().Unix(),
timeoutSec: timeoutSec,
})
}

57
govue/config.go

@ -0,0 +1,57 @@
package govue
import (
"errors"
"git.ouxuan.net/3136352472/govue2/pool"
)
type Config struct {
Addr string `json:"addr"`
StaticDir string `json:"static_dir"`
UseJsFile string `json:"use_js_file"`
ErrorPage404 string `json:"error_page_404"`
Mode string `json:"mode"`
Pool pool.Config `json:"pool"`
}
func GenerateConfig(path string) (err error) {
if pathExists(path) {
err = errors.New("配置文件已存在")
return
}
config := Config{
Addr: "0.0.0.0:8080",
StaticDir: "static",
UseJsFile: "static/use.js",
Mode: "release",
Pool: pool.Config{
MaxIdle: 2000,
MinIdle: 5,
MaxTotal: 6000,
},
}
jsonToFile(path, config)
return
}
func GetConfig(path string) (c *Config, err error) {
c = &Config{
Addr: "0.0.0.0:8080",
StaticDir: "static",
UseJsFile: "static/use.js",
Mode: "release",
Pool: pool.Config{
MaxIdle: 2000,
MinIdle: 5,
MaxTotal: 6000,
},
}
if pathExists(path) {
jsonByFile(path, c)
}
return
}

1
govue/govue-js-src/.gitignore

@ -0,0 +1 @@
node_modules

8
govue/govue-js-src/build.js

@ -0,0 +1,8 @@
var fs = require("fs");
var envify = require('envify/custom')
var browserify = require("browserify");
browserify("./govue.js")
.transform({ global: true }, envify({ NODE_ENV: 'production' }))
.bundle()
.pipe(fs.createWriteStream("../govue-runtime/govue.js"));

16
govue/govue-js-src/govue.js

@ -0,0 +1,16 @@
var process = { env: { VUE_ENV: "server", NODE_ENV: "production" } }; this.global = { process: process };
Promise = require('es6-promise').Promise;
require('Vue');
require('vue-router')
require('vue-server-renderer')
require('axios');
require('qs');
require('axios/lib/helpers/buildURL');
require('axios/lib/core/buildFullPath');
require('axios/lib/core/settle');
require('axios/lib/core/createError');
require("htmlparser2");
GV.init = function(cb){
cb(require)
}

1424
govue/govue-js-src/package-lock.json
File diff suppressed because it is too large
View File

17
govue/govue-js-src/package.json

@ -0,0 +1,17 @@
{
"dependencies": {
"axios": "^0.19.2",
"browserify": "^16.5.1",
"buffer": "^4.9.2",
"envify": "^4.1.0",
"es6-promise": "^4.2.8",
"htmlparser2": "^4.1.0",
"qs": "^6.9.4",
"vue": "^2.6.11",
"vue-router": "^3.3.4",
"vue-server-renderer": "^2.6.11"
},
"scripts": {
"build": "node build.js && cd .. && go-bindata-assetfs --pkg=govue govue-runtime/..."
}
}

40550
govue/govue-runtime/govue.js
File diff suppressed because it is too large
View File

178
govue/govue-runtime/header.js

@ -0,0 +1,178 @@
var GV = {};
var isBrowser = false;
var Promise;
var GoUseCallback;
var GoUse = function (cb) {
GoUseCallback = "with(this){(" + cb.toString() + ")()}";
}
var net = {
request: function (c) {
var url = c["url"];
var data = c["data"];
var header = c["header"];
var method = c["method"];
var dataType = c["dataType"];
var timeout = c["timeout"];
var success = c["success"];
var fail = c["fail"];
var complete = c["complete"];
var isHost = url.indexOf("http://") > -1 || url.indexOf("https://") > -1;
var isAbs = (url[0] == '/');
if (!isHost) {
if (!isAbs) {
url = location.pathname + "/" + url
}
url = location.host + "/" + url;
for (; url.indexOf("//") > -1;) {
url = url.replace("//", "/")
}
url = location.protocol + "//" + url
}
if (data) {
if (method == "GET") {
url = $.param.querystring(url, data);
data = ""
} else {
if (typeof (data) == "object") {
if (header["Content-Type"].indexOf("application/json") > -1 || header["content-type"].indexOf("application/json") > -1) {
data = JSON.stringify(data);
} else {
data = $.param(data);
}
}
}
}
var resp = GoRequest({
url: url,
data: data,
method: method,
header: header,
timeout: timeout
});
resp = JSON.parse(resp);
var statusCode = resp["statusCode"];
var result = resp["data"];
if (dataType == "json") {
result = JSON.parse(result)
}
if (statusCode == 200) {
success && success({
data: result,
statusCode: statusCode + "",
});
} else {
fail && fail({
data: result,
statusCode: statusCode + "",
})
}
}
};
var axiosUse = function (ctx) {
var buildURL = ctx.require('axios/lib/helpers/buildURL');
var buildFullPath = ctx.require('axios/lib/core/buildFullPath');
var settle = ctx.require('axios/lib/core/settle');
var createError = ctx.require('axios/lib/core/createError');
function transformResponse(mpResponse, config, mpRequestOption) {
var headers = mpResponse.header || mpResponse.headers;
var status = mpResponse.statusCode || mpResponse.status;
var statusText = '';
if (status === 200) {
statusText = 'OK';
} else if (status === 400) {
statusText = 'Bad Request';
}
var response = {
data: mpResponse.data,
status: status,
statusText: statusText,
headers: headers,
config: config,
request: mpRequestOption
};
return response;
}
ctx.axios.defaults.adapter = function (config) {
return new Promise(function (resolve, reject) {
var mpRequestOption = {
url: buildURL(buildFullPath(config.baseURL, config.url), config.params, config.paramsSerializer),
method: config["method"] ? config["method"].toUpperCase() : "get",
data: config["data"],
header: config["headers"],
timeout: config["timeout"],
success: function (mpResponse) {
var response = transformResponse(mpResponse, config, mpRequestOption);
settle(resolve, reject, response);
},
fail: function (error) {
reject(createError(error.data));
},
};
net.request(mpRequestOption)
})
};
}
var GetGoVueTemplate = function (raw) {
var tagStart = "<!--gv-start-->";
var tagEnd = "<!--gv-end-->";
var start = raw.indexOf(tagStart)
if (start < 0) {
return
}
var end = raw.indexOf(tagEnd)
if (end < 0) {
throw "注释未闭合"
return
}
var content = raw.substring(start + tagStart.length, end);
var template = raw.substring(0, start) + "<!--vue-ssr-outlet-->\n{{{GoVueTemplate}}}\n{{{GoVueData}}}" + raw.substr(end + tagEnd.length);
return {
template: template,
content: content,
}
}
var GetGoVueJsCode = function (htmlparser2, raw) {
var codes = [];
var flag = false;
var parser = new htmlparser2.Parser(
{
onopentag: function (name, attribs) {
flag = (name === "script" && (attribs["gv-src"] || attribs["gv-src"] === ""))
if (flag) {
if (attribs.src) {
codes.push(GoReadFile(attribs.src))
}else{
codes.push("")
}
}
// console.log("name", name, attribs["gv-src"]);
},
ontext: function (text) {
if (flag && text && text !== "") {
codes[codes.length - 1] += text
}
// console.log("-->", text);
},
},
{decodeEntities: true}
);
parser.write(raw)
parser.end();
return codes;
}

7203
govue/govue-runtime/polyfill.js
File diff suppressed because it is too large
View File

60
govue/govue-runtime/runtime.js

@ -0,0 +1,60 @@
GV.init(function (_require) {
new (function () {
this.require = _require;
this.Vue = _require('Vue');
this.VueRouter = _require('vue-router');
this.Vue.use(this.VueRouter)
this.axios = _require('axios');
var htmlparser2 = _require('htmlparser2');
axiosUse(this);
this.qs = _require('qs');
this.GoVueMount = function (app, context) {
var ctx = GetGoVueTemplate(GoHtmlSrc)
app.$options.template = ctx.content;
// console.log(ctx.template)
var renderer = _require('vue-server-renderer').createRenderer({
template: ctx.template
})
if (!context) {
context = {}
}
context.GoVueTemplate = "<go-vue-template style='display: none'>" + Base64Encode(encodeURIComponent(ctx.content)) + "</go-vue-template>"
context.GoVueData = "<go-vue-data style='display: none'>" + Base64Encode(encodeURIComponent(JSON.stringify(app.$data))) + "</go-vue-data>"
renderer.renderToString(app, context, function (err, html) {
if (err) {
throw err
} else {
GoReturn(html);
}
})
}
try {
eval(GoUseCallback)
} catch (e) {
GoSetError(JSON.stringify({
code: GoUseCallback,
error: e.toString(),
}))
throw e
}
var codes = GetGoVueJsCode(htmlparser2, GoHtmlSrc)
// console.log(JSON.stringify(codes))
for (var i in codes) {
// console.log("with(this){\n" + codes[i] + "\n}");
try {
eval("with(this){\n" + codes[i] + "\n}")
} catch (e) {
GoSetError(JSON.stringify({
code: "with(this){\n" + codes[i] + "\n}",
error: e.toString(),
}))
throw e
}
}
})()
})

211
govue/govue.go

@ -0,0 +1,211 @@
package govue
//go-bindata-assetfs --pkg=govue govue-runtime/...
import (
"fmt"
"git.ouxuan.net/3136352472/govue2/jsruntime"
"git.ouxuan.net/3136352472/govue2/pool"
"github.com/elazarl/go-bindata-assetfs"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"time"
)
type GoVue struct {
StaticPath string
UseJsPath string
ErrorPage404 string
Resources *assetfs.AssetFS
IsDebugMode bool
jsRuntimePool *pool.JsRuntimePool
CacheSec int64
}
func NewGoVueDefaultConfig(debug bool) (gv *GoVue, err error) {
gv = &GoVue{
Resources: assetFS(),
IsDebugMode: debug,
}
err = gv.initRender(debug)
return
}
func NewGoVue(useJsPath string, staticPath string, debug bool) (gv *GoVue, err error) {
gv = &GoVue{
StaticPath: staticPath,
UseJsPath: useJsPath,
Resources: assetFS(),
IsDebugMode: debug,
}
err = gv.initRender(debug)
return
}
func (gv *GoVue) SetCacheSec(cacheSec int64) {
gv.CacheSec = cacheSec
}
func (gv *GoVue) SetErrorPage404(page string) {
gv.ErrorPage404 = page
}
func SetPoolConfig(config pool.Config) {
pool.DefaultConfig = config
}
func (gv *GoVue) initRender(debug bool) (err error) {
if gv.StaticPath == "" {
gv.StaticPath = filepath.Join(getSelfFilePath(), "static")
}
if gv.UseJsPath == "" {
gv.UseJsPath = filepath.Join(gv.StaticPath, "use.js")
}
headerScript, err := gv.Resources.Asset(filepath.Join("govue-runtime", "header.js"))
if err != nil {
return
}
polyfill, err := gv.Resources.Asset(filepath.Join("govue-runtime", "polyfill.js"))
if err != nil {
return
}
mainScript, err := gv.Resources.Asset(filepath.Join("govue-runtime", "runtime.js"))
if err != nil {
return
}
govueScriptFile := "govue.js"
govueScript, err := gv.Resources.Asset(filepath.Join("govue-runtime", govueScriptFile))
if err != nil {
return
}
gv.jsRuntimePool = pool.NewJsRuntimePool(string(mainScript), gv.UseJsPath, gv.StaticPath, jsruntime.Relys{
jsruntime.Rely{
Src: string(headerScript),
},
jsruntime.Rely{
Src: string(polyfill),
},
jsruntime.Rely{
Src: string(govueScript),
},
}, jsruntime.ModeSync, debug)
return
}
func (gv *GoVue) PoolLog() {
gv.jsRuntimePool.Log()
}
func (gv *GoVue) StartPoolLog() {
go func() {
defer func() {
recover()
log.Println("统计协程异常")
}()
lastidle := 0
lastactive := 0
for {
all, idle, active := gv.jsRuntimePool.NumInfo()
if idle != lastidle || active != lastactive {
fmt.Println("渲染协程数量变动变动:所有:", all, "空闲:", idle, "活动:", active)
}
lastidle = idle
lastactive = active
time.Sleep(time.Second)
}
}()
}
func (gv *GoVue) LoadStaticResources(request *http.Request, goExtDataS ...interface{}) (result []byte, err error) {
cacheKey := fmt.Sprintf("%s|%s", request.URL.Path, request.URL.RawQuery)
if gv.CacheSec > 0 {
html := GetCache(cacheKey)
if html != "" {
//log.Println("加载缓存")
return []byte(html), nil
}
}
//log.Println("非缓存", cacheKey)
var staticDir string
var filePath = request.URL.Path
if gv.StaticPath == "" {
staticDir = filepath.Join(getSelfFilePath(), "static")
} else {
staticDir = gv.StaticPath
}
filePath = filepath.Join(staticDir, filePath)
fi, err := os.Stat(filePath)
if err == nil {
if fi.IsDir() {
defaultPath := []string{"index.html"}
for e := range defaultPath {
path := filepath.Join(filePath, defaultPath[e])
_, err := os.Stat(path)
if err == nil {
filePath = path
break
}
}
}
}
result, err = ioutil.ReadFile(filePath)
if err != nil {
result, err = gv.renderErrPage(404, request, goExtDataS...)
if gv.CacheSec > 0 {
SetCache(cacheKey, string(result), gv.CacheSec)
}
return
}
if filepath.Ext(filePath) != ".html" {
return
}
err = gv.jsRuntimePool.JsRuntimeCall(func(jr *jsruntime.JsRuntime) {
err = jr.Render(filePath, fmt.Sprintf("http://%s%s", request.Host, request.RequestURI), string(result), nil, func(data string) {
result = []byte(data)
if gv.CacheSec > 0 {
SetCache(cacheKey, string(result), gv.CacheSec)
}
})
if err != nil {
result = []byte(err.Error())
}
})
return
}
func (gv *GoVue) renderErrPage(errCode int, request *http.Request, goExtDataS ...interface{}) (result []byte, err error) {
filePath := gv.ErrorPage404
if filePath == "" {
return []byte("页面不存在"), err
}
result, _ = ioutil.ReadFile(filePath)
var goExtData interface{}
if len(goExtDataS) > 0 {
goExtData = goExtDataS[0]
}
err = gv.jsRuntimePool.JsRuntimeCall(func(jr *jsruntime.JsRuntime) {
err = jr.Render(filePath, fmt.Sprintf("http://%s%s", request.Host, request.RequestURI), string(result), goExtData, func(data string) {
result = []byte(data)
})
})
return
}

58
govue/utils.go

@ -0,0 +1,58 @@
package govue
import (
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
func pathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
var selfFilePath string
func getSelfFilePath() string {
if selfFilePath == "" {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return ""
}
path, err := filepath.Abs(file)
if err != nil {
return ""
}
i := strings.LastIndex(path, "/")
if i < 0 {
i = strings.LastIndex(path, "\\")
}
if i < 0 {
return ""
}
selfFilePath, _ = filepath.Abs(string(path[0 : i+1]))
}
return selfFilePath
}
func jsonToFile(file string, v interface{}) bool {
data, err := json.Marshal(v)
if err != nil {
return false
}
return ioutil.WriteFile(file, data, 0644) == nil
}
func jsonByFile(file string, v interface{}) {
data, _ := ioutil.ReadFile(file)
json.Unmarshal(data, v)
}

11
jsruntime/console.go

@ -0,0 +1,11 @@
package jsruntime
import (
"github.com/dop251/goja_nodejs/console"
"github.com/dop251/goja_nodejs/require"
)
func (jr *JsRuntime) EnableConsoleFun() {
new(require.Registry).Enable(jr.runtime)
console.Enable(jr.runtime)
}

9
jsruntime/cookie.go

@ -0,0 +1,9 @@
package jsruntime
import "log"
func (jr *JsRuntime) EnableCookieFun() {
jr.runtime.Set("setInterval", func() {
log.Println("禁用setInterval")
})
}

146
jsruntime/go_request.go

@ -0,0 +1,146 @@
package jsruntime
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func (jr *JsRuntime) EnableRequestFunc() {
jr.runtime.Set("GoRequest", func(call map[string]interface{}) (respData string) {
url := ""
if call["url"] != nil {
url = call["url"].(string)
}
data := ""
if call["data"] != nil {
data = call["data"].(string)
}
header := map[string]interface{}{}
if call["header"] != nil {
header = call["header"].(map[string]interface{})
}
timeout := 0
if call["timeout"] != nil {
switch call["timeout"].(type) {
case int64:
timeout = int(call["timeout"].(int64))
case int:
timeout = call["timeout"].(int)
}
}
method := "GET"
if call["method"] != nil {
method = call["method"].(string)
}
client := &http.Client{
Timeout: time.Millisecond * time.Duration(timeout),
}
var contentReader *bytes.Reader
contentReader = bytes.NewReader([]byte(data))
//log.Println(method, url, data)
req, err := http.NewRequest(method, url, contentReader)
if err != nil {
rs, _ := json.Marshal(map[string]interface{}{
"statusCode": 504,
"data": err.Error(),
})
return string(rs)
}
for e := range header {
req.Header.Set(e, header[e].(string))
}
resp, err := client.Do(req)
var statusCode int
var respDatas string
if err == nil {
defer resp.Body.Close()
statusCode = resp.StatusCode
rb, _ := ioutil.ReadAll(resp.Body)
respDatas = string(rb)
} else {
statusCode = 502
respDatas = err.Error()
}
log2File("netword", "-------------------------------------------------")
log2File("netword", "网络请求:", method, url)
headerStr, _ := json.Marshal(header)
log2File("netword", "网络请求头:", string(headerStr))
log2File("netword", "网络请求发送数据:", data)
log2File("netword", "请求返回状态:", err == nil)
log2File("netword", "请求返回状态码:", statusCode)
log2File("netword", "请求返回:", respDatas)
log2File("netword", "-------------------------------------------------")
rs, _ := json.Marshal(map[string]interface{}{
"statusCode": statusCode,
"data": string(respDatas),
})
return string(rs)
})
}
func log2File(tag string, strs ...interface{}) {
str := fmt.Sprintln(strs...)
path := filepath.Join(getSelfFilePath(), fmt.Sprintf("jsruntime-%s.log", tag))
if pathExists(path) {
raw, _ := ioutil.ReadFile(path)
str = string(raw) + str
}
ioutil.WriteFile(path, []byte(str), 0644)
}
func pathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
var selfFilePath string
func getSelfFilePath() string {
if selfFilePath == "" {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return ""
}
path, err := filepath.Abs(file)
if err != nil {
return ""
}
i := strings.LastIndex(path, "/")
if i < 0 {
i = strings.LastIndex(path, "\\")
}
if i < 0 {
return ""
}
selfFilePath, _ = filepath.Abs(string(path[0 : i+1]))
}
return selfFilePath
}

12
jsruntime/interval.go

@ -0,0 +1,12 @@
package jsruntime
import "log"
func (jr *JsRuntime) EnableIntervalFun() {
jr.runtime.Set("setInterval", func() {
log.Println("禁用setInterval")
})
jr.runtime.Set("clearInterval", func() {
log.Println("禁用setInterval")
})
}

291
jsruntime/runtime.go

@ -0,0 +1,291 @@
package jsruntime
import (
"crypto/md5"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/dop251/goja"
"html"
"io/ioutil"
"log"
"net/url"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
type JsRuntime struct {
MainSrc string
UseSrcFun func() string
ExtFileDir string
Relys Relys
TimeoutSec int64
runtime *goja.Runtime
mutex sync.Mutex
RenderCallBackFun func(string)
IsDebugModel bool
isRenderIng bool
}
type Rely struct {
Src string
}
type Relys []Rely
type RunMode int
const (
ModeSync RunMode = 1
//ModeAsync RunMode = 2
)
func NewJsRuntime(mainScript string, extFileDir string, rels Relys, mode RunMode) (jr *JsRuntime, err error) {
jr = &JsRuntime{
MainSrc: mainScript,
Relys: rels,
ExtFileDir: extFileDir,
TimeoutSec: 30,
}
jr.runtime = goja.New()
jr.EnableTimeoutFunc()
jr.EnableIntervalFun()
jr.EnableRequestFunc()
jr.EnableConsoleFun()
jr.SetVariable("Base64Decode", func(text string) string {
code, _ := base64.StdEncoding.DecodeString(text)
return string(code)
})
jr.SetVariable("Base64Encode", func(text string) string {
code := base64.StdEncoding.EncodeToString([]byte(text))
return code
})
jr.SetVariable("GoReadFile", func(filePath string) string {
code, _ := ioutil.ReadFile(filepath.Join(jr.ExtFileDir, filePath))
return string(code)
})
err = jr.InitJsCode()
if err != nil {
log.Println("InitJsCode", err)
}
return jr, nil
}
func (jr *JsRuntime) PrintError(msg string, code string) string {
template := `
<body style="background: #eeeeee;padding: 5%%;width:85%%;">
<h3 style="color: red;">%s</h3>
<div style="background: #fff;">
<pre style="width:95%%;font-size: 14px; padding: 2%%; ">%s<pre>
</div>
</body>
`
log.Println("PrintError:", msg)
if !jr.IsDebugModel {
return fmt.Sprintf(template, "502", "服务器错误")
}
var errLine string
var line = -1
evalErr := strings.Split(msg, "<eval>")
if len(evalErr) > 1 {
evalErr = strings.Split(evalErr[1], ":")
if len(evalErr) > 1 {
line, _ = strconv.Atoi(evalErr[1])
if fmt.Sprintf("%d", line) != evalErr[1] {
line = -1
}
}
}
if line == -1 {
evalErr = strings.Split(msg, ": Line ")
if len(evalErr) > 1 {
evalErr = strings.Split(evalErr[1], ":")
if len(evalErr) > 0 {
line, _ = strconv.Atoi(evalErr[0])
if fmt.Sprintf("%d", line) != evalErr[0] {
line = -1
}
}
}
}
//log.Println("error line", line, evalErr)
if line > -1 {
lines := strings.Split(string(code), "\n")
linesStart := line - 15
linesEnd := line + 15
if linesStart < 0 {
linesStart = 0
}
if len(lines)-1 < linesEnd {
linesEnd = len(lines)
}
if linesStart >= linesEnd {
errLine = "无法定位的报错位置"
linesStart = 0
} else {
//
//log.Println("error line linesStart", linesStart)
//log.Println("error line linesEnd", linesEnd)
//linesEnd = linesStart + linesEnd
for e := range lines[linesStart:linesEnd] {
if e+linesStart+1 == line {
lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: >>>>>> %s`, e+linesStart, lines[linesStart:linesEnd][e])
} else {
lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: %s`, e+linesStart, lines[linesStart:linesEnd][e])
}
}
errLine = strings.Join(lines[linesStart:linesEnd], "\n")
}
} else {
lines := strings.Split(string(code), "\n")
for e := range lines {
lines[e] = fmt.Sprintf(`%2d: %s`, e, lines[e])
}
errLine = strings.Join(lines, "\n")
}
return fmt.Sprintf(template, html.EscapeString(msg), html.EscapeString(errLine))
}
func (jr *JsRuntime) InitJsCode() error {
for e := range jr.Relys {
//start := time.Now().UnixNano()
err := jr.RunCode(jr.Relys[e].Src)
//end := time.Now().UnixNano()
//log.Println(e, "加载时间:", end-start)
if err != nil {
return err
}
}
return nil
}
func (jr *JsRuntime) SetVariable(name string, value interface{}) {
jr.runtime.Set(name, value)
}
var codeMap sync.Map
func (jr *JsRuntime) md5(data string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(data)))
}
func (jr *JsRuntime) RunCode(code string) error {
id := jr.md5(code)
var p interface{}
var ok bool
p, ok = codeMap.Load(id)
if !ok {
pro, err := goja.Compile("", code, false)
if err != nil {
log.Println("Compile Error")
return err
}
codeMap.Store(id, pro)
p = pro
}
program := p.(*goja.Program)
_, err := jr.runtime.RunProgram(program)
//log.Println("RunCode result", result)
return err
}
func (jr *JsRuntime) Render(filePath, href, tplSrc string, GoExtData interface{}, cb func(data string)) (err error) {
jr.RenderCallBackFun = cb
jr.mutex.Lock()
defer jr.mutex.Unlock()
runtime := jr.runtime
mainSrc := jr.MainSrc
isLock := true
jr.isRenderIng = true
//timeoutSec := jr.TimeoutSec
defer func() {
jr.isRenderIng = false
isLock = false
jr.runtime.ClearInterrupt()
}()
var useSrc string
if jr.UseSrcFun != nil {
useSrc = jr.UseSrcFun()
}
if useSrc != "" {
err = jr.RunCode(useSrc)
if err != nil {
return err
}
}
urls, err := url.Parse(href)
if err != nil {
return err
}
url2, err := url.Parse(strings.Replace(filePath, `\`, "/", -1))
if err != nil {
return err
}
runtime.Set("GoExtData", GoExtData)
runtime.Set("GoHtmlSrc", tplSrc)
runtime.Set("GoHref", href)
runtime.Set("GoHost", urls.Host)
runtime.Set("GoQuery", urls.Query().Encode())
runtime.Set("GoPath", url2.Path)
runtimeError := map[string]string{}
runtime.Set("GoReturn", func(data string) {
jr.RenderCallBackFun(data)
})
runtime.Set("GoSetError", func(data string) {
//log.Println("GoSetError",data)
json.Unmarshal([]byte(data), &runtimeError)
})
t := time.AfterFunc(time.Duration(jr.TimeoutSec)*time.Second, func() {
if jr.isRenderIng {
jr.runtime.Interrupt("time out")
jr.runtime.ClearInterrupt()
}
})
defer t.Stop()
err = jr.RunCode(mainSrc)
if err != nil {
jr.runtime.ClearInterrupt()
return errors.New(jr.PrintError(runtimeError["error"], runtimeError["code"]))
}
return
}

28
jsruntime/timeout.go

@ -0,0 +1,28 @@
package jsruntime
import (
"github.com/dop251/goja"
"log"
)
func (jr *JsRuntime) EnableTimeoutFunc() {
jr.runtime.Set("setTimeout", func(call goja.FunctionCall) goja.Value {
if fn, ok := goja.AssertFunction(call.Argument(0)); ok {
//delay := call.Argument(1).ToInteger()
var args []goja.Value
if len(call.Arguments) > 2 {
args = call.Arguments[2:]
}
//
//time.Sleep(time.Duration(delay) * time.Millisecond)
fn(nil, args...)
}
return jr.runtime.ToValue(123124512)
})
jr.runtime.Set("clearTimeout", func(i int64) {
log.Println("clearTimeout暂未支持")
})
}

120
pool/pool.go

@ -0,0 +1,120 @@
package pool
import (
"context"
"git.ouxuan.net/3136352472/govue2/jsruntime"
"github.com/jolestar/go-commons-pool"
"io/ioutil"
"log"
"time"
)
type JsRuntimePool struct {
ctx context.Context
jsRuntimePool *pool.ObjectPool
}
type Config struct {
MaxIdle int `json:"max_idle"`
MinIdle int `json:"min_idle"`
MaxTotal int `json:"max_total"`
}
var DefaultConfig Config
var useSrc string
func NewJsRuntimePool(mainScript string, useFileName string, staticPath string, relys jsruntime.Relys, mode jsruntime.RunMode, debug bool) (jrp *JsRuntimePool) {
jrp = &JsRuntimePool{}
factory := pool.NewPooledObjectFactorySimple(
func(context.Context) (interface{}, error) {
render, err := jsruntime.NewJsRuntime(mainScript, staticPath, relys, mode)
if err == nil {
render.IsDebugModel = debug
render.UseSrcFun = func() string {
return useSrc
}
}
return render, err
})
jrp.ctx = context.Background()
if DefaultConfig.MaxIdle == 0 {
DefaultConfig.MaxIdle = 3000
}
if DefaultConfig.MinIdle == 0 {
DefaultConfig.MinIdle = 20
}
if DefaultConfig.MaxTotal == 0 {
DefaultConfig.MaxTotal = 200
}
jrp.jsRuntimePool = pool.NewObjectPoolWithDefaultConfig(jrp.ctx, factory)
jrp.jsRuntimePool.Config.LIFO = false
jrp.jsRuntimePool.Config.MaxIdle = DefaultConfig.MaxIdle
jrp.jsRuntimePool.Config.MinIdle = DefaultConfig.MinIdle
jrp.jsRuntimePool.Config.MaxTotal = DefaultConfig.MaxTotal
jrp.jsRuntimePool.Config.BlockWhenExhausted = true
var preparePoolTask func()
preparePoolTask = func() {
defer func() {
recover()
time.Sleep(time.Second)
preparePoolTask()
}()
for {
jrp.jsRuntimePool.PreparePool(jrp.ctx)
time.Sleep(time.Second)
}
}
go preparePoolTask()
var useSrcTask func()
useSrcTask = func() {
defer func() {
recover()
time.Sleep(time.Second)
useSrcTask()
}()
for {
raw, _ := ioutil.ReadFile(useFileName)
useSrc = string(raw)
time.Sleep(time.Second * 3)
}
}
go useSrcTask()
return
}
func (jrp *JsRuntimePool) JsRuntimeCall(call func(jr *jsruntime.JsRuntime)) (err error) {
obj, err := jrp.jsRuntimePool.BorrowObject(jrp.ctx)
if err != nil {
return
}
o := obj.(*jsruntime.JsRuntime)
call(o)
err = jrp.jsRuntimePool.ReturnObject(jrp.ctx, obj)
return
}
func (jrp *JsRuntimePool) Log() {
idle := jrp.jsRuntimePool.GetNumIdle()
active := jrp.jsRuntimePool.GetNumActive()
all := idle + active
log.Println("所有:", all, "空闲:", idle, "活动:", active)
}
func (jrp *JsRuntimePool) NumInfo() (int, int, int) {
idle := jrp.jsRuntimePool.GetNumIdle()
active := jrp.jsRuntimePool.GetNumActive()
all := idle + active
return all, idle, active
}

95
static/example1/index.html

@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>{{ title }}</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!--gv-start-->
<div>
<a href="#" @click="click(message)">{{message}}</a>
<div id="app-4">
<ol>
<li v-for="todo in todos">
<a href="#" @click="click(todo.text)"> {{ todo.text }}</a>
</li>
</ol>
</div>
<input v-model="message" placeholder="edit me">
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
<!--gv-end-->
</body>
<script src="../js/govue.js"></script>
<script gv-src>
var app = new Vue({
data: {
message: 'Hello',
todos: [
{text: '学习 JavaScript'},
{text: '学习 Vue'},
{text: '整个牛项目'},
{text: '{{a}}'}
],
checkedNames: [],
picked: '',
selected: 'A',
options: [
{text: 'One', value: 'A'},
{text: 'Two', value: 'B'},
{text: 'Three', value: 'C'}
]
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
},
methods: {
click: function (a) {
alert("点击触发:" + a);
},
},
});
GoVueMount(app, {
title: "123"
}, "body > div")
</script>
</html>

100
static/index.html

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<script src="js/govue.js"></script>
<script src="js/vue.js"></script>
<style>
html, body {
margin: 0;
padding: 0;
color: #888888;
}
.content {
text-align: center;
}
.content h1 {
font-size: 68px;
}
.content p {
font-size: 28px;
}
pre {
display: inline-block;
border-radius: 4px;
background: black;
color: white;
text-align: left;
padding: 20px;
width: 600px;
height: 300px;
overflow: auto;
}
pre::-webkit-scrollbar {
width: 0 !important
}
a{
text-decoration: none;
color: darkslategrey;
}
</style>
</head>
<body>
<!--gv-start-->
<div id="app">
<div class="content">
<govue-title></govue-title>
<p>{{desc}}</p>
<div>当前版本号:{{version}}</div>
<br>
<a href="javascript:" @click="click">点击 {{isShowCode?'隐藏':'显示'}}</a>
<div v-if="isShowCode">
<pre>{{code}}</pre>
</div>
</div>
</div>
<!--gv-end-->
<script gv-src>
Vue.component('govue-title', {
template: '<h1 id="title">govue</h1>'
});
if (isBrowser) {
GoHtmlSrc = ""
}
var app = new Vue({
data: {
desc: "基础golang开发的一套vue服务端渲染方案",
version: "0",
code: GoHtmlSrc,
isShowCode: true
},
methods: {
click: function () {
this.isShowCode = !this.isShowCode
},
},
});
if (!isBrowser) {
console.log(GoHost + "/govue.version")
axios.get("http://" + GoHost + "/govue.version").then(function (data) {
app.version = data.data.version
})
}
GoVueMount(app, {
title: "govue"
}, "#app")
</script>
</body>
</html>

20
static/js/govue.js

@ -0,0 +1,20 @@
window.isBrowser = true;
function GoVueMount(app, ctx, el) {
var sourceTemplate = document.getElementsByTagName("go-vue-template");
var GoVueHTML = "";
if (sourceTemplate.length > 0) {
GoVueHTML = decodeURIComponent(atob(sourceTemplate[0].innerHTML))
}
var sourceData = document.getElementsByTagName("go-vue-data");
var GoVueData = {};
if (sourceData.length > 0) {
GoVueData = JSON.parse(decodeURIComponent(atob(sourceData[0].innerHTML)))
}
app.$options.template = GoVueHTML;
app.$mount(el);
for (var i in GoVueData) {
app[i] = GoVueData[i];
}
}

11965
static/js/vue.js
File diff suppressed because it is too large
View File

Loading…
Cancel
Save