29 changed files with 63082 additions and 4 deletions
-
6.gitignore
-
3README.md
-
21client/govue.js
-
115cmd/main.go
-
327govue/bindata.go
-
34govue/cache.go
-
57govue/config.go
-
1govue/govue-js-src/.gitignore
-
8govue/govue-js-src/build.js
-
16govue/govue-js-src/govue.js
-
1424govue/govue-js-src/package-lock.json
-
17govue/govue-js-src/package.json
-
40550govue/govue-runtime/govue.js
-
178govue/govue-runtime/header.js
-
7203govue/govue-runtime/polyfill.js
-
60govue/govue-runtime/runtime.js
-
211govue/govue.go
-
58govue/utils.go
-
11jsruntime/console.go
-
9jsruntime/cookie.go
-
146jsruntime/go_request.go
-
12jsruntime/interval.go
-
291jsruntime/runtime.go
-
28jsruntime/timeout.go
-
120pool/pool.go
-
95static/example1/index.html
-
100static/index.html
-
20static/js/govue.js
-
11965static/js/vue.js
@ -1,2 +1 @@ |
|||
# govue2 |
|||
|
|||
# go-vue |
@ -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]; |
|||
} |
|||
} |
@ -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
File diff suppressed because it is too large
View File
@ -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, |
|||
}) |
|||
} |
@ -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 |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
node_modules |
@ -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")); |
@ -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
File diff suppressed because it is too large
View File
@ -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
File diff suppressed because it is too large
View File
@ -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
File diff suppressed because it is too large
View File
@ -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 |
|||
} |
|||
} |
|||
})() |
|||
}) |
@ -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 |
|||
} |
@ -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) |
|||
} |
@ -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) |
|||
} |
@ -0,0 +1,9 @@ |
|||
package jsruntime |
|||
|
|||
import "log" |
|||
|
|||
func (jr *JsRuntime) EnableCookieFun() { |
|||
jr.runtime.Set("setInterval", func() { |
|||
log.Println("禁用setInterval") |
|||
}) |
|||
} |
@ -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 |
|||
} |
@ -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") |
|||
}) |
|||
} |
@ -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 |
|||
} |
@ -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暂未支持") |
|||
}) |
|||
} |
@ -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 |
|||
} |
@ -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> |
@ -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> |
@ -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
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue