sptc 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/sptcd.js +17 -3
- package/dist/httpServer.js +67 -13
- package/engine/index.js +4 -0
- package/engine/interpreter.js +34 -3
- package/package.json +1 -1
- package/tests/www/cc.s +5 -0
- package/tests/www/{index.sjs → index.s} +14 -0
- package/utils/compileFile.js +6 -4
- package/utils/dir.js +46 -0
- package/utils/index.js +1 -0
package/bin/sptcd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const {FastCGI, FastCGI_FPM}=require('../dist/httpServer')
|
|
4
4
|
const {Command}=require('commander')
|
|
@@ -14,6 +14,7 @@ const program=new Command()
|
|
|
14
14
|
.option('-e, --exts <string>', 'Specify the valid extensions of executable sptc files.', '.s,.sjs,.sptc')
|
|
15
15
|
.option('-n, --workers <number>', 'Workers count.', 1)
|
|
16
16
|
.option('-s, --slient', 'Slient mode.')
|
|
17
|
+
.option('-t, --traverse', 'If pathname is a directory, execute the index file under the pathname directory if any index file exists. If no index file is found, traverse the directory. If a router file is specified, this option will be ignored.')
|
|
17
18
|
.action(({
|
|
18
19
|
port,
|
|
19
20
|
locally,
|
|
@@ -22,26 +23,39 @@ const program=new Command()
|
|
|
22
23
|
exts,
|
|
23
24
|
workers,
|
|
24
25
|
slient,
|
|
26
|
+
traverse,
|
|
25
27
|
})=>{
|
|
28
|
+
const disableLog=slient || require('cluster').isWorker
|
|
29
|
+
|
|
30
|
+
if(router && traverse) {
|
|
31
|
+
traverse=false
|
|
32
|
+
if(!disableLog) {
|
|
33
|
+
console.log('`--traverse` option has been ignored due to the router file has been specified.')
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
const argv=[port, locally, {
|
|
27
38
|
serverDir: workdir,
|
|
28
39
|
routerEntry: router,
|
|
29
40
|
exts: exts.split(',').map(x=>x.trim()).filter(x=>x),
|
|
30
41
|
debug: true,
|
|
42
|
+
traverse,
|
|
31
43
|
}]
|
|
44
|
+
|
|
32
45
|
if(workers>1) {
|
|
33
46
|
FastCGI_FPM(workers, ...argv)
|
|
34
47
|
}else{
|
|
35
48
|
FastCGI(...argv)
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
if(
|
|
51
|
+
if(disableLog) return;
|
|
39
52
|
const {getLocalIpv4Addresses}=require('../utils')
|
|
40
53
|
console.log('`sptc-http-server` has been launched with the following option:')
|
|
41
54
|
const o={
|
|
42
55
|
workdir,
|
|
43
|
-
routerEntry: router,
|
|
56
|
+
routerEntry: router || null,
|
|
44
57
|
}
|
|
58
|
+
o.traverse=traverse? true: false
|
|
45
59
|
if(locally) {
|
|
46
60
|
o.serve='127.0.0.1:'+port
|
|
47
61
|
}else{
|
package/dist/httpServer.js
CHANGED
|
@@ -3,6 +3,7 @@ const {
|
|
|
3
3
|
safe_path,
|
|
4
4
|
fileExists,
|
|
5
5
|
getExtension,
|
|
6
|
+
listdir,
|
|
6
7
|
}=require('../utils')
|
|
7
8
|
const {executeSptcFile}=require('../engine')
|
|
8
9
|
|
|
@@ -42,26 +43,51 @@ function CGI(req, res, option) {
|
|
|
42
43
|
exts=['.s', '.sptc', '.sjs'],
|
|
43
44
|
debug=false,
|
|
44
45
|
router=null,
|
|
46
|
+
traverse=false,
|
|
45
47
|
env,
|
|
46
48
|
}=option
|
|
47
|
-
const [reqCtx, handler]=buildRequestContext(req, res, {
|
|
49
|
+
const [reqCtx, handler]=buildRequestContext(req, res, {
|
|
50
|
+
srvDir,
|
|
51
|
+
debug,
|
|
52
|
+
env,
|
|
53
|
+
isRouterMode: router,
|
|
54
|
+
})
|
|
48
55
|
const file=reqCtx.$_REQUEST_FILE || {}
|
|
49
56
|
const fn=router || file.fullname
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
|
|
58
|
+
function _executeFile(fn) {
|
|
59
|
+
const ext=getExtension(fn)
|
|
60
|
+
if(!exts.includes(ext)) {
|
|
61
|
+
res.writeHead(403, {'content-type': 'text/plain'})
|
|
62
|
+
res.end(`extensions does not match: \`${ext}\``)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
executeSptcFile(fn, reqCtx, handler)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if(fileExists(fn)) {
|
|
69
|
+
_executeFile(fn)
|
|
53
70
|
return
|
|
54
71
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
|
|
73
|
+
if(!traverse) {
|
|
74
|
+
res.writeHead(404, {'content-type': 'text/html'})
|
|
75
|
+
res.end(`<h3>File does not exist: \`${router || file.pathname}\`</h3>`)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for(let e of exts) {
|
|
80
|
+
if(!fileExists(fn+'/index'+e)) continue
|
|
81
|
+
_executeFile(fn+'/index'+e)
|
|
82
|
+
return
|
|
60
83
|
}
|
|
61
|
-
|
|
84
|
+
|
|
85
|
+
res.writeHead(404, {'content-type': 'text/html'})
|
|
86
|
+
res.end(traverseDirectory(srvDir, file.pathname, exts))
|
|
87
|
+
|
|
62
88
|
}
|
|
63
89
|
|
|
64
|
-
function buildRequestContext(req, res, {srvDir, debug, env}) {
|
|
90
|
+
function buildRequestContext(req, res, {srvDir, debug, env, isRouterMode}) {
|
|
65
91
|
const {fullname, pathname}=getRequestFile(req, srvDir)
|
|
66
92
|
const reqCtx={
|
|
67
93
|
$_RAW_REQUEST: req,
|
|
@@ -105,7 +131,7 @@ function buildRequestContext(req, res, {srvDir, debug, env}) {
|
|
|
105
131
|
}
|
|
106
132
|
reqCtx.isDebug=_=>state.is_debug
|
|
107
133
|
|
|
108
|
-
|
|
134
|
+
const handler={
|
|
109
135
|
write: x=>{
|
|
110
136
|
flushHeader()
|
|
111
137
|
res.write(x)
|
|
@@ -119,7 +145,10 @@ function buildRequestContext(req, res, {srvDir, debug, env}) {
|
|
|
119
145
|
flushHeader()
|
|
120
146
|
res.end(state.is_debug? e.stack: '')
|
|
121
147
|
},
|
|
122
|
-
|
|
148
|
+
isEntry: true,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return [reqCtx, handler]
|
|
123
152
|
}
|
|
124
153
|
function getRequestFile(req, srvDir) {
|
|
125
154
|
const _url=require('url')
|
|
@@ -130,6 +159,31 @@ function getRequestFile(req, srvDir) {
|
|
|
130
159
|
}
|
|
131
160
|
}
|
|
132
161
|
|
|
162
|
+
function traverseDirectory(ref, dir, exts) {
|
|
163
|
+
let ret=`
|
|
164
|
+
<!doctype html>
|
|
165
|
+
<meta charset=utf8 />
|
|
166
|
+
<style>*{line-height: 1.8;}b{font-size: 16px;}a{font-size: 18px; color: #2222ff; cursor: pointer;}a.dir{color: #555555;}</style>`
|
|
167
|
+
const {dirs, files, error}=listdir(ref, dir)
|
|
168
|
+
ret+=`<a style="font-size: 22px;" href="${dir!=='/'? '../': ''}">back</a>`
|
|
169
|
+
ret+=`<h3>${dir}</h3>`
|
|
170
|
+
if(error) {
|
|
171
|
+
ret+=`<h3>${error.message}</h3>`
|
|
172
|
+
}else{
|
|
173
|
+
for(let i=0; i<dirs.length; i++) {
|
|
174
|
+
ret+=`<a class="dir" href="${encodeURIComponent(dirs[i].fn)}/">${dirs[i].fn}</a> <br/>`
|
|
175
|
+
}
|
|
176
|
+
for(let i=0; i<files.length; i++) {
|
|
177
|
+
if(exts.includes(getExtension(files[i].fn))) {
|
|
178
|
+
ret+=`<a href="${encodeURIComponent(files[i].fn)}">${files[i].fn}</a> <br/>`
|
|
179
|
+
}else{
|
|
180
|
+
ret+=`<b>${files[i].fn}</b> ${files[i].size} <br/>`
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return ret
|
|
185
|
+
}
|
|
186
|
+
|
|
133
187
|
module.exports={
|
|
134
188
|
FastCGI,
|
|
135
189
|
FastCGI_FPM,
|
package/engine/index.js
CHANGED
|
@@ -17,6 +17,10 @@ function executeSptcFile(filename, payload, option={}) {
|
|
|
17
17
|
undefined,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
if(option.isEntry) {
|
|
21
|
+
_option.isEntry=option.isEntry
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const [ctx0, vm]=Compiler.compileSptcFile(filename, _option)
|
|
21
25
|
const [ctx, priv]=Interpreter.buildContext({...payload, ...ctx0}, option)
|
|
22
26
|
Interpreter.executeVm(vm, ctx, priv)
|
package/engine/interpreter.js
CHANGED
|
@@ -133,6 +133,7 @@ function buildContext(ctx0, option) {
|
|
|
133
133
|
onError=NOOP,
|
|
134
134
|
masterPriv=null,
|
|
135
135
|
readAsBinary=false,
|
|
136
|
+
isEntry=false,
|
|
136
137
|
}=option
|
|
137
138
|
|
|
138
139
|
const ctx=buildGlobal()
|
|
@@ -144,6 +145,9 @@ function buildContext(ctx0, option) {
|
|
|
144
145
|
_defers: [],
|
|
145
146
|
__autoload_func: null,
|
|
146
147
|
__autoload_vars: {},
|
|
148
|
+
_defined: {},
|
|
149
|
+
cacheVersion: '',
|
|
150
|
+
statExpires: -1,
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
Object.assign(ctx, {
|
|
@@ -152,6 +156,16 @@ function buildContext(ctx0, option) {
|
|
|
152
156
|
isMaster: !masterPriv,
|
|
153
157
|
})
|
|
154
158
|
|
|
159
|
+
// Dynamically set the cache configuration.
|
|
160
|
+
ctx.configSptcFileCache=(cacheVersion, cacheSeconds)=>{
|
|
161
|
+
if(!isEntry) {
|
|
162
|
+
console.log('Warning: this function only works in the entry file.')
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
priv.cacheVersion=cacheVersion
|
|
166
|
+
priv.statExpires=cacheSeconds
|
|
167
|
+
}
|
|
168
|
+
|
|
155
169
|
ctx.echo=(...x)=>{
|
|
156
170
|
if(priv.isEnd) return;
|
|
157
171
|
priv.echos.push(...x)
|
|
@@ -184,6 +198,13 @@ function buildContext(ctx0, option) {
|
|
|
184
198
|
priv._defers.push(fn)
|
|
185
199
|
}
|
|
186
200
|
|
|
201
|
+
ctx.define=(k, v)=>{
|
|
202
|
+
if(k in priv._defined) {
|
|
203
|
+
throw new Error(`duplicate key: ${k}`)
|
|
204
|
+
}
|
|
205
|
+
priv._defined[k]=v
|
|
206
|
+
}
|
|
207
|
+
|
|
187
208
|
try{
|
|
188
209
|
ctx.require=_module.createRequire(ctx0.__filefullname)
|
|
189
210
|
}catch(e) {
|
|
@@ -202,13 +223,20 @@ function buildContext(ctx0, option) {
|
|
|
202
223
|
|
|
203
224
|
ctx.include=(inc_filename, inc_payload={})=>{
|
|
204
225
|
const inc=path.resolve(ctx0.__dirname, inc_filename)
|
|
205
|
-
const [_ctx0, _vm]=Compiler.compileSptcFile(inc
|
|
206
|
-
|
|
226
|
+
const [_ctx0, _vm]=Compiler.compileSptcFile(inc, {
|
|
227
|
+
statExpires: priv.statExpires,
|
|
228
|
+
cacheVersion: priv.cacheVersion,
|
|
229
|
+
})
|
|
230
|
+
const [_ctx, _]=buildContext({
|
|
231
|
+
...ctx0,
|
|
232
|
+
...inc_payload,
|
|
233
|
+
..._ctx0,
|
|
234
|
+
}, {
|
|
207
235
|
write: option.write,
|
|
208
|
-
// end: ctx.end,
|
|
209
236
|
onError: option.onError,
|
|
210
237
|
masterPriv: priv,
|
|
211
238
|
readAsBinary,
|
|
239
|
+
isEntry: false,
|
|
212
240
|
})
|
|
213
241
|
const ret=executeVm(_vm, _ctx, priv)
|
|
214
242
|
return Object.assign({}, _ctx._exports, {__getter__: (...x)=>ret.constructor('return '+(x.length>1? '['+x.join(',')+']': x))()})
|
|
@@ -222,6 +250,9 @@ function buildContext(ctx0, option) {
|
|
|
222
250
|
pctx=new Proxy(pctx, {
|
|
223
251
|
get: (target, prop, receiver)=>{
|
|
224
252
|
if(false===prop in target) {
|
|
253
|
+
if(prop in priv._defined) {
|
|
254
|
+
return priv._defined[prop]
|
|
255
|
+
}
|
|
225
256
|
if(prop in priv.__autoload_vars) {
|
|
226
257
|
return priv.__autoload_vars[prop]
|
|
227
258
|
}
|
package/package.json
CHANGED
package/tests/www/cc.s
ADDED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
<?js
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
$_GLOBAL['x']='999x'
|
|
5
|
+
configSptcFileCache('eob0', 100)
|
|
6
|
+
|
|
2
7
|
echo($_REQUEST_FILE)
|
|
8
|
+
|
|
9
|
+
define('DDD', 2)
|
|
10
|
+
include(__dirname+'/cc.s')
|
|
11
|
+
|
|
12
|
+
echo('oaawecR=>'+RRR, Math.random())
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
|
|
3
16
|
echo('aaaa<pre>')
|
|
4
17
|
var_dump($_GLOBAL, this)
|
|
5
18
|
|
|
@@ -15,3 +28,4 @@ Sync.Push((async _=>{
|
|
|
15
28
|
}
|
|
16
29
|
echo('--done--')
|
|
17
30
|
})())
|
|
31
|
+
*/
|
package/utils/compileFile.js
CHANGED
|
@@ -3,23 +3,25 @@ const {mtime, readTextFile}=require('./base')
|
|
|
3
3
|
const _caches={}
|
|
4
4
|
function compileFile(filename, option) {
|
|
5
5
|
const {
|
|
6
|
-
cacheKeyWrapper=x=>x,
|
|
7
6
|
contentWrapper=x=>x,
|
|
8
7
|
mockFileContent=null,
|
|
9
8
|
compileFunc=x=>x,
|
|
10
|
-
statExpires=-1,
|
|
9
|
+
statExpires=-1, cacheVersion='',
|
|
11
10
|
customCache,
|
|
12
11
|
}=option
|
|
13
12
|
|
|
13
|
+
const cacheKeyWrapper=statExpires>0?
|
|
14
|
+
fn=>(option.cacheVersion || '')+'-'+fn:
|
|
15
|
+
fn=>fn
|
|
16
|
+
|
|
14
17
|
if(mockFileContent) {
|
|
15
18
|
return compileFunc(contentWrapper(mockFileContent))
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
const caches=customCache || _caches
|
|
19
|
-
|
|
20
22
|
const cacheKey=cacheKeyWrapper(filename)
|
|
21
23
|
let _e=caches[filename]
|
|
22
|
-
const file_mt=(!_e || statExpires<=0 ||
|
|
24
|
+
const file_mt=(!_e || statExpires<=0 || Date.now()-_e.modify_t>statExpires*1e3)?
|
|
23
25
|
mtime(filename):
|
|
24
26
|
_e.file_mt
|
|
25
27
|
|
package/utils/dir.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const fs=require('fs')
|
|
2
|
+
const {safe_path}=require('./base')
|
|
3
|
+
|
|
4
|
+
function fsize(b) {
|
|
5
|
+
const u=['b', 'kb', 'mb', 'gb']
|
|
6
|
+
let i=0;
|
|
7
|
+
for(; i<u.length-1; i++) {
|
|
8
|
+
if(b<1024) break
|
|
9
|
+
b/=1024
|
|
10
|
+
}
|
|
11
|
+
return b.toFixed(1)+u[i]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function listdir(ref, dir) {
|
|
15
|
+
const ret={
|
|
16
|
+
dirs: [],
|
|
17
|
+
files: [],
|
|
18
|
+
}
|
|
19
|
+
try{
|
|
20
|
+
const ls=fs.readdirSync(safe_path(ref, dir))
|
|
21
|
+
for(let a of ls) try{
|
|
22
|
+
const fn=safe_path(ref, dir+'/'+a)
|
|
23
|
+
const stat=fs.statSync(fn)
|
|
24
|
+
const dfn=safe_path(dir.substr(1), a)
|
|
25
|
+
if(stat.isFile()) {
|
|
26
|
+
ret.files.push({
|
|
27
|
+
dfn,
|
|
28
|
+
fn: a,
|
|
29
|
+
size: fsize(stat.size),
|
|
30
|
+
})
|
|
31
|
+
}else{
|
|
32
|
+
ret.dirs.push({
|
|
33
|
+
dfn,
|
|
34
|
+
fn: a,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}catch(e) {}
|
|
38
|
+
}catch(e) {
|
|
39
|
+
ret.error=new Error('`'+dir+'` permission denied')
|
|
40
|
+
}
|
|
41
|
+
return ret
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports={
|
|
45
|
+
listdir,
|
|
46
|
+
}
|