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