ranuts 0.1.0-alpha → 0.1.0-alpha.1
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/assets/img/sort/bubble.gif +0 -0
- package/assets/img/sort/complexity.png +0 -0
- package/assets/img/sort/select.gif +0 -0
- package/assets/img/sort/sort.png +0 -0
- package/assets/img/tree/balanceTree.png +0 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.js +1078 -44
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +1 -1
- package/dist/index.umd.cjs.map +1 -1
- package/dist/src/file/fileInfo.d.ts +7 -0
- package/dist/src/file/fs.d.ts +2 -0
- package/dist/src/file/readDir.d.ts +6 -0
- package/dist/src/file/readFile.d.ts +8 -0
- package/dist/src/file/watchFile.d.ts +8 -0
- package/dist/src/file/writeFile.d.ts +8 -0
- package/dist/src/mode/subscribe.d.ts +16 -0
- package/dist/src/ranlog/behavior.d.ts +1 -0
- package/dist/src/ranlog/env.d.ts +5 -0
- package/dist/src/ranlog/error.d.ts +2 -0
- package/dist/src/ranlog/index.d.ts +26 -0
- package/dist/src/ranlog/performance.d.ts +21 -0
- package/dist/src/ranlog/report.d.ts +7 -0
- package/dist/src/ranlog/request.d.ts +18 -0
- package/dist/src/ranlog/utils.d.ts +19 -0
- package/dist/src/utils/filterObj.d.ts +8 -0
- package/dist/src/utils/str2Xml.d.ts +8 -0
- package/dist/src/vnode/h.d.ts +6 -0
- package/dist/src/vnode/hooks.d.ts +23 -0
- package/dist/src/vnode/htmlDomApi.d.ts +33 -0
- package/dist/src/vnode/init.d.ts +2 -0
- package/dist/src/vnode/is.d.ts +5 -0
- package/dist/src/vnode/modules/attributes.d.ts +8 -0
- package/dist/src/vnode/modules/class.d.ts +8 -0
- package/dist/src/vnode/modules/index.d.ts +7 -0
- package/dist/src/vnode/modules/listeners.d.ts +14 -0
- package/dist/src/vnode/modules/props.d.ts +8 -0
- package/dist/src/vnode/modules/style.d.ts +14 -0
- package/dist/src/vnode/vnode.d.ts +31 -0
- package/package.json +32 -9
- package/readme.md +1 -0
- package/src/astParser/Parser.ts +654 -0
- package/src/astParser/Tokenizer.ts +447 -0
- package/src/astParser/nodeTypes.ts +194 -0
- package/src/astParser/utils.ts +27 -0
- package/src/babel/parser.ts +10 -0
- package/src/colors/fmt.ts +29 -0
- package/src/colors/isColorSupported.ts +13 -0
- package/src/file/appendFile.ts +29 -0
- package/src/file/fileInfo.ts +22 -0
- package/src/file/fs.ts +9 -0
- package/src/file/readDir.ts +18 -0
- package/src/file/readFile.ts +26 -0
- package/src/file/watchFile.ts +31 -0
- package/src/file/writeFile.ts +38 -0
- package/src/mode/subscribe.ts +89 -0
- package/src/ran/commit.ts +0 -0
- package/src/ran/dom.ts +0 -0
- package/src/ran/hooks.ts +0 -0
- package/src/ran/reconcile.ts +0 -0
- package/src/ran/schedule.ts +0 -0
- package/src/ranlog/behavior.ts +5 -0
- package/src/ranlog/console.ts +16 -0
- package/src/ranlog/env.ts +29 -0
- package/src/ranlog/error.ts +11 -0
- package/src/ranlog/performance.ts +65 -0
- package/src/ranlog/report.ts +33 -0
- package/src/ranlog/request.ts +60 -0
- package/src/ranlog/utils.ts +92 -0
- package/src/ranpack/ast/Declaration.ts +120 -0
- package/src/ranpack/ast/Node.ts +7 -0
- package/src/ranpack/ast/Reference.ts +32 -0
- package/src/ranpack/ast/Scope.ts +80 -0
- package/src/ranpack/bundle.ts +50 -0
- package/src/ranpack/graph.ts +143 -0
- package/src/ranpack/module.ts +431 -0
- package/src/ranpack/moduleLoader.ts +73 -0
- package/src/ranpack/plugins.ts +70 -0
- package/src/ranpack/statement.ts +66 -0
- package/src/ranpack/utils/buildScope.ts +77 -0
- package/src/ranpack/utils/findReference.ts +36 -0
- package/src/ranpack/utils/isFunctionDeclaration.ts +41 -0
- package/src/ranpack/utils/makeLegalIdentifier.ts +18 -0
- package/src/ranpack/utils/object.ts +8 -0
- package/src/ranpack/utils/resolve.ts +32 -0
- package/src/ranpack/utils/walk.ts +66 -0
- package/src/ranpack/ws.ts +269 -0
- package/src/server/connectType.ts +9 -0
- package/src/server/encodeUrl.ts +46 -0
- package/src/server/escapeHtml.ts +46 -0
- package/src/server/get.ts +36 -0
- package/src/server/jitter.ts +77 -0
- package/src/server/mimeType.ts +858 -0
- package/src/server/paresUrl.ts +40 -0
- package/src/server/send.ts +89 -0
- package/src/server/server.ts +67 -0
- package/src/server/status.ts +191 -0
- package/src/server/traverse.ts +54 -0
- package/src/server/websocket.ts +200 -0
- package/src/sort/bubble.ts +21 -0
- package/src/sort/bucket.ts +11 -0
- package/src/sort/count.ts +10 -0
- package/src/sort/heap.ts +10 -0
- package/src/sort/insert.ts +20 -0
- package/src/sort/merge.ts +35 -0
- package/src/sort/quick.ts +50 -0
- package/src/sort/radix.ts +11 -0
- package/src/sort/randomArray.ts +21 -0
- package/src/sort/select.ts +23 -0
- package/src/sort/shell.ts +22 -0
- package/src/utils/compose.ts +37 -0
- package/src/utils/filterObj.ts +21 -0
- package/src/utils/mergeObj.ts +12 -0
- package/src/utils/startTask.ts +15 -0
- package/src/utils/str2Xml.ts +26 -0
- package/src/utils/taskEnd.ts +14 -0
- package/src/vnode/h.ts +108 -0
- package/src/vnode/hooks.ts +25 -0
- package/src/vnode/htmlDomApi.ts +199 -0
- package/src/vnode/init.ts +429 -0
- package/src/vnode/is.ts +17 -0
- package/src/vnode/modules/attributes.ts +70 -0
- package/src/vnode/modules/class.ts +46 -0
- package/src/vnode/modules/listeners.ts +123 -0
- package/src/vnode/modules/props.ts +41 -0
- package/src/vnode/modules/style.ts +118 -0
- package/src/vnode/vnode.ts +65 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http'
|
|
2
|
+
|
|
3
|
+
interface ParseUrl {
|
|
4
|
+
search?: string
|
|
5
|
+
query?: string
|
|
6
|
+
pathname?: string
|
|
7
|
+
path?: string
|
|
8
|
+
href?: string
|
|
9
|
+
_raw?: string
|
|
10
|
+
}
|
|
11
|
+
interface Req extends IncomingMessage {
|
|
12
|
+
_parsedUrl?: ParseUrl
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @description: 解析 IncomingMessage 类型的请求url,返回的类型永远是 ParseUrl
|
|
16
|
+
* @param {Req} req
|
|
17
|
+
* @return {ParseUrl}
|
|
18
|
+
*/
|
|
19
|
+
export default function (req: Req): ParseUrl | undefined {
|
|
20
|
+
const url = req.url
|
|
21
|
+
if (url === void 0) return
|
|
22
|
+
|
|
23
|
+
let obj = req._parsedUrl
|
|
24
|
+
if (obj && obj._raw === url) return obj
|
|
25
|
+
|
|
26
|
+
obj = {}
|
|
27
|
+
obj.query = obj.search = undefined
|
|
28
|
+
obj.href = obj.path = obj.pathname = url
|
|
29
|
+
|
|
30
|
+
const idx = url.indexOf('?', 1)
|
|
31
|
+
if (idx !== -1) {
|
|
32
|
+
obj.search = url.substring(idx)
|
|
33
|
+
obj.query = obj.search.substring(1)
|
|
34
|
+
obj.pathname = url.substring(0, idx)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
obj._raw = url
|
|
38
|
+
|
|
39
|
+
return (req._parsedUrl = obj)
|
|
40
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
4
|
+
import type { MiddlewareFunction, Next } from '@/server/server'
|
|
5
|
+
import { queryMime, setMime } from '@/node/http/mimeType'
|
|
6
|
+
|
|
7
|
+
interface Option {
|
|
8
|
+
pathname: string
|
|
9
|
+
fileTypes: Record<string, string>
|
|
10
|
+
}
|
|
11
|
+
const staticMiddleware = (option: Partial<Option> = {}): MiddlewareFunction => {
|
|
12
|
+
const { pathname, fileTypes = {} } = option
|
|
13
|
+
return async (
|
|
14
|
+
req: IncomingMessage,
|
|
15
|
+
res: ServerResponse,
|
|
16
|
+
next: Next,
|
|
17
|
+
): Promise<void> => {
|
|
18
|
+
try {
|
|
19
|
+
if (req.url) {
|
|
20
|
+
const htmlContentType = 'text/html'
|
|
21
|
+
// 获取传入的地址,如果没有,取当前的目录
|
|
22
|
+
const dirPath = pathname ? pathname : process.cwd()
|
|
23
|
+
// 静态资源文件根路径
|
|
24
|
+
const root = path.normalize(path.resolve(dirPath))
|
|
25
|
+
// 获取访问的文件类型
|
|
26
|
+
const extension = path.extname(req.url).slice(1)
|
|
27
|
+
// 增加mimeType
|
|
28
|
+
Object.keys(fileTypes).forEach((key) =>
|
|
29
|
+
setMime(key, fileTypes[key]),
|
|
30
|
+
)
|
|
31
|
+
// 文件类型后缀
|
|
32
|
+
const type = extension ? queryMime(extension) : htmlContentType
|
|
33
|
+
// 是否支持的文件类型
|
|
34
|
+
const supportedExtension = Boolean(type)
|
|
35
|
+
// 如果这个文件类型不允许访问,则直接返回404
|
|
36
|
+
if (!supportedExtension) {
|
|
37
|
+
res.writeHead(404, { 'Content-Type': 'text/html' })
|
|
38
|
+
res.end('404: File not found')
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 通过url获取访问的文件名称
|
|
43
|
+
let fileName = req.url
|
|
44
|
+
// 如果访问的路径是 /
|
|
45
|
+
if (req.url === '/') {
|
|
46
|
+
// 则文件名是 index.html
|
|
47
|
+
fileName = 'index.html'
|
|
48
|
+
// 如果访问的文件类型不在允许的类型里面,默认返回index.html
|
|
49
|
+
} else if (!extension) {
|
|
50
|
+
try {
|
|
51
|
+
// 检测文件是否允许访问
|
|
52
|
+
fs.accessSync(path.join(root, req.url + '.html'), fs.constants.F_OK)
|
|
53
|
+
// 当允许访问时,则返回对应的页面
|
|
54
|
+
fileName = req.url + '.html'
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// 否则直接返回 index.html
|
|
57
|
+
fileName = path.join(req.url, 'index.html')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 有文件名且访问的文件类型也允许访问
|
|
61
|
+
const filePath = path.join(root, fileName)
|
|
62
|
+
const isPathUnderRoot = path
|
|
63
|
+
.normalize(path.resolve(filePath))
|
|
64
|
+
.startsWith(root)
|
|
65
|
+
|
|
66
|
+
if (!isPathUnderRoot) {
|
|
67
|
+
res.writeHead(404, { 'Content-Type': 'text/html' })
|
|
68
|
+
res.end('404: File not found')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fs.readFile(filePath, (err, data) => {
|
|
73
|
+
if (err) {
|
|
74
|
+
res.writeHead(404, { 'Content-Type': 'text/html' })
|
|
75
|
+
res.end('404: File not found')
|
|
76
|
+
} else {
|
|
77
|
+
res.writeHead(200, { 'Content-Type': type })
|
|
78
|
+
res.end(data)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
} else {
|
|
82
|
+
console.log('request has not url')
|
|
83
|
+
}
|
|
84
|
+
await next()
|
|
85
|
+
} catch (error) {}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default staticMiddleware
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
|
+
import http from 'node:http'
|
|
3
|
+
|
|
4
|
+
export type Next = () => Promise<never> | Promise<void>
|
|
5
|
+
|
|
6
|
+
export type MiddlewareFunction = (
|
|
7
|
+
req: IncomingMessage,
|
|
8
|
+
res: ServerResponse,
|
|
9
|
+
next: Next,
|
|
10
|
+
) => void | Promise<void>
|
|
11
|
+
|
|
12
|
+
class Server {
|
|
13
|
+
stack: Array<MiddlewareFunction>
|
|
14
|
+
constructor() {
|
|
15
|
+
this.stack = []
|
|
16
|
+
/**
|
|
17
|
+
* @description: 添加中间件
|
|
18
|
+
*/
|
|
19
|
+
}
|
|
20
|
+
use(handle: MiddlewareFunction): void {
|
|
21
|
+
if (!handle) {
|
|
22
|
+
throw new Error('the use function has an incorrect argument')
|
|
23
|
+
}
|
|
24
|
+
this.stack.push(handle)
|
|
25
|
+
}
|
|
26
|
+
listen(
|
|
27
|
+
...args: any
|
|
28
|
+
): http.Server<typeof IncomingMessage, typeof ServerResponse> {
|
|
29
|
+
const fn = compose(this.stack)
|
|
30
|
+
const server = http.createServer((req, res) => {
|
|
31
|
+
fn(req, res).then().catch(onerror)
|
|
32
|
+
})
|
|
33
|
+
return server.listen(...args)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onerror(err: Error) {
|
|
38
|
+
console.error(err.stack || err.toString())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function compose(middleware: Array<MiddlewareFunction>) {
|
|
42
|
+
if (!Array.isArray(middleware))
|
|
43
|
+
throw new TypeError('Middleware stack must be an array!')
|
|
44
|
+
for (const fn of middleware) {
|
|
45
|
+
if (typeof fn !== 'function')
|
|
46
|
+
throw new TypeError('Middleware must be composed of functions!')
|
|
47
|
+
}
|
|
48
|
+
return function (req: IncomingMessage, res: ServerResponse, next?: Next) {
|
|
49
|
+
let index = -1
|
|
50
|
+
function dispatch(i: number): Promise<never> | Promise<void> {
|
|
51
|
+
if (i <= index)
|
|
52
|
+
return Promise.reject(new Error('next() called multiple times'))
|
|
53
|
+
index = i
|
|
54
|
+
let fn = middleware[i]
|
|
55
|
+
if (i === middleware.length && next) fn = next
|
|
56
|
+
if (!fn) return Promise.resolve()
|
|
57
|
+
try {
|
|
58
|
+
return Promise.resolve(fn(req, res, dispatch.bind(null, i + 1)))
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return Promise.reject(err)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return dispatch(0)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default Server
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// code map message
|
|
2
|
+
const codes = new Map([
|
|
3
|
+
[100, 'Continue'],
|
|
4
|
+
[101, 'Switching Protocols'],
|
|
5
|
+
[102, 'Processing'],
|
|
6
|
+
[103, 'Early Hints'],
|
|
7
|
+
[200, 'OK'],
|
|
8
|
+
[201, 'Created'],
|
|
9
|
+
[202, 'Accepted'],
|
|
10
|
+
[203, 'Non-Authoritative Information'],
|
|
11
|
+
[204, 'No Content'],
|
|
12
|
+
[205, 'Reset Content'],
|
|
13
|
+
[206, 'Partial Content'],
|
|
14
|
+
[207, 'Multi-Status'],
|
|
15
|
+
[208, 'Already Reported'],
|
|
16
|
+
[226, 'IM Used'],
|
|
17
|
+
[300, 'Multiple Choices'],
|
|
18
|
+
[301, 'Moved Permanently'],
|
|
19
|
+
[302, 'Found'],
|
|
20
|
+
[303, 'See Other'],
|
|
21
|
+
[304, 'Not Modified'],
|
|
22
|
+
[305, 'Use Proxy'],
|
|
23
|
+
[307, 'Temporary Redirect'],
|
|
24
|
+
[308, 'Permanent Redirect'],
|
|
25
|
+
[400, 'Bad Request'],
|
|
26
|
+
[401, 'Unauthorized'],
|
|
27
|
+
[402, 'Payment Required'],
|
|
28
|
+
[403, 'Forbidden'],
|
|
29
|
+
[404, 'Not Found'],
|
|
30
|
+
[405, 'Method Not Allowed'],
|
|
31
|
+
[406, 'Not Acceptable'],
|
|
32
|
+
[407, 'Proxy Authentication Required'],
|
|
33
|
+
[408, 'Request Timeout'],
|
|
34
|
+
[409, 'Conflict'],
|
|
35
|
+
[410, 'Gone'],
|
|
36
|
+
[411, 'Length Required'],
|
|
37
|
+
[412, 'Precondition Failed'],
|
|
38
|
+
[413, 'Payload Too Large'],
|
|
39
|
+
[414, 'URI Too Long'],
|
|
40
|
+
[415, 'Unsupported Media Type'],
|
|
41
|
+
[416, 'Range Not Satisfiable'],
|
|
42
|
+
[417, 'Expectation Failed'],
|
|
43
|
+
[418, "I'm a Teapot"],
|
|
44
|
+
[421, 'Misdirected Request'],
|
|
45
|
+
[422, 'Unprocessable Entity'],
|
|
46
|
+
[423, 'Locked'],
|
|
47
|
+
[424, 'Failed Dependency'],
|
|
48
|
+
[425, 'Too Early'],
|
|
49
|
+
[426, 'Upgrade Required'],
|
|
50
|
+
[428, 'Precondition Required'],
|
|
51
|
+
[429, 'Too Many Requests'],
|
|
52
|
+
[431, 'Request Header Fields Too Large'],
|
|
53
|
+
[451, 'Unavailable For Legal Reasons'],
|
|
54
|
+
[500, 'Internal Server Error'],
|
|
55
|
+
[501, 'Not Implemented'],
|
|
56
|
+
[502, 'Bad Gateway'],
|
|
57
|
+
[503, 'Service Unavailable'],
|
|
58
|
+
[504, 'Gateway Timeout'],
|
|
59
|
+
[505, 'HTTP Version Not Supported'],
|
|
60
|
+
[506, 'Variant Also Negotiates'],
|
|
61
|
+
[507, 'Insufficient Storage'],
|
|
62
|
+
[508, 'Loop Detected'],
|
|
63
|
+
[509, 'Bandwidth Limit Exceeded'],
|
|
64
|
+
[510, 'Not Extended'],
|
|
65
|
+
[511, 'Network Authentication Required'],
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
const status = {
|
|
69
|
+
// status code to message map
|
|
70
|
+
message: codes,
|
|
71
|
+
// status message (lower-case) to code map
|
|
72
|
+
code: createMessageToStatusCodeMap(codes),
|
|
73
|
+
// array of status codes
|
|
74
|
+
codes: createStatusCodeList(codes),
|
|
75
|
+
// status codes for redirects
|
|
76
|
+
redirect: {
|
|
77
|
+
300: true,
|
|
78
|
+
301: true,
|
|
79
|
+
302: true,
|
|
80
|
+
303: true,
|
|
81
|
+
305: true,
|
|
82
|
+
307: true,
|
|
83
|
+
308: true,
|
|
84
|
+
},
|
|
85
|
+
// status codes for empty bodies
|
|
86
|
+
empty: {
|
|
87
|
+
204: true,
|
|
88
|
+
205: true,
|
|
89
|
+
304: true,
|
|
90
|
+
},
|
|
91
|
+
// status codes for when you should retry the request
|
|
92
|
+
retry: {
|
|
93
|
+
502: true,
|
|
94
|
+
503: true,
|
|
95
|
+
504: true,
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @private
|
|
101
|
+
* @description: Create a map of message to status code.
|
|
102
|
+
* @param {Record} codes
|
|
103
|
+
* @param {*} string
|
|
104
|
+
* @return {Record<string, number>}
|
|
105
|
+
*/
|
|
106
|
+
function createMessageToStatusCodeMap(codes: Map<number, string>) {
|
|
107
|
+
const map: Map<string, number> = new Map()
|
|
108
|
+
for (const [status, message] of codes) {
|
|
109
|
+
map.set(message.toLowerCase(), status)
|
|
110
|
+
}
|
|
111
|
+
return map
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create a list of all status codes.
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
function createStatusCodeList(codes: Map<number, string>) {
|
|
120
|
+
const codeList: Array<number> = []
|
|
121
|
+
for (const [status, _] of codes) {
|
|
122
|
+
codeList.push(status)
|
|
123
|
+
}
|
|
124
|
+
return codeList
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get the status code for given message.
|
|
129
|
+
* @private
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
function getStatusCode(message: string) {
|
|
133
|
+
const msg = message.toLowerCase()
|
|
134
|
+
status.code.has(msg)
|
|
135
|
+
if (!status.code.has(msg)) {
|
|
136
|
+
throw new Error('invalid status message: "' + message + '"')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return status.code.get(msg)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the status message for given code.
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
function getStatusMessage(code: number) {
|
|
148
|
+
if (!status.message.has(code)) {
|
|
149
|
+
throw new Error('invalid status code: ' + code)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return status.message.get(code)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get the status code.
|
|
157
|
+
*
|
|
158
|
+
* Given a number, this will throw if it is not a known status
|
|
159
|
+
* code, otherwise the code will be returned. Given a string,
|
|
160
|
+
* the string will be parsed for a number and return the code
|
|
161
|
+
* if valid, otherwise will lookup the code assuming this is
|
|
162
|
+
* the status message.
|
|
163
|
+
*
|
|
164
|
+
* @param {string|number} code
|
|
165
|
+
* @returns {number}
|
|
166
|
+
* @public
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
function getStatus(code?: number | string): number | string | undefined {
|
|
170
|
+
if (typeof code === 'number') {
|
|
171
|
+
// return message
|
|
172
|
+
return getStatusMessage(code)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof code !== 'string') {
|
|
176
|
+
throw new TypeError('code must be a number or string')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// '403'
|
|
180
|
+
const n = parseInt(code, 10)
|
|
181
|
+
if (!isNaN(n) && status.codes.includes(n)) {
|
|
182
|
+
// return message
|
|
183
|
+
return getStatusMessage(n)
|
|
184
|
+
}
|
|
185
|
+
// return code
|
|
186
|
+
return getStatusCode(code)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default status
|
|
190
|
+
|
|
191
|
+
export { getStatus }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path'
|
|
2
|
+
import type { Stats } from 'node:fs'
|
|
3
|
+
import { readdir, readdirSync, stat, statSync } from 'node:fs'
|
|
4
|
+
import { promisify } from 'node:util'
|
|
5
|
+
|
|
6
|
+
const toStats = promisify(stat)
|
|
7
|
+
const toRead = promisify(readdir)
|
|
8
|
+
|
|
9
|
+
type Caller = (relPath: string, absPath: string, stats: Stats) => any
|
|
10
|
+
/**
|
|
11
|
+
* @description: 递归遍历每一个目录,为找到的文件都执行一个函数
|
|
12
|
+
* @param {string} dir
|
|
13
|
+
* @param {Caller} callback
|
|
14
|
+
* @param {*} pre
|
|
15
|
+
*/
|
|
16
|
+
export async function traverse(
|
|
17
|
+
dir: string,
|
|
18
|
+
callback: Caller,
|
|
19
|
+
pre = '',
|
|
20
|
+
): Promise<any> {
|
|
21
|
+
dir = resolve('.', dir)
|
|
22
|
+
await toRead(dir).then((arr) => {
|
|
23
|
+
return Promise.all(
|
|
24
|
+
arr.map((str) => {
|
|
25
|
+
const abs = join(dir, str)
|
|
26
|
+
return toStats(abs).then((stats) => {
|
|
27
|
+
return stats.isDirectory()
|
|
28
|
+
? traverse(abs, callback, join(pre, str))
|
|
29
|
+
: callback(join(pre, str), abs, stats)
|
|
30
|
+
})
|
|
31
|
+
}),
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @description: 同步方法,递归遍历每一个目录,为找到的文件都执行一个函数
|
|
37
|
+
* @param {string} dir
|
|
38
|
+
* @param {Caller} callback
|
|
39
|
+
* @param {*} pre
|
|
40
|
+
*/
|
|
41
|
+
export function traverseSync(dir: string, callback: Caller, pre = ''): void {
|
|
42
|
+
dir = resolve('.', dir)
|
|
43
|
+
const arr = readdirSync(dir)
|
|
44
|
+
let i = 0,
|
|
45
|
+
abs,
|
|
46
|
+
stats
|
|
47
|
+
for (; i < arr.length; i++) {
|
|
48
|
+
abs = join(dir, arr[i])
|
|
49
|
+
stats = statSync(abs)
|
|
50
|
+
stats.isDirectory()
|
|
51
|
+
? traverseSync(abs, callback, join(pre, arr[i]))
|
|
52
|
+
: callback(join(pre, arr[i]), abs, stats)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import http from 'node:http'
|
|
2
|
+
import crypto from 'node:crypto'
|
|
3
|
+
import type internal from 'node:stream'
|
|
4
|
+
import EventEmitter from '@/designMode/EventEmitter'
|
|
5
|
+
|
|
6
|
+
interface FrameMeta {
|
|
7
|
+
fin: boolean;
|
|
8
|
+
opcode: number;
|
|
9
|
+
mask: boolean;
|
|
10
|
+
maskKey: Buffer | null;
|
|
11
|
+
payloadSize: number;
|
|
12
|
+
size: number;
|
|
13
|
+
metaSize: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sixteenBitsMax = 65535 // 65536 is the max of 16 bits
|
|
17
|
+
|
|
18
|
+
const opcodeMap = {
|
|
19
|
+
text: 129 // 129 = 10000001, opcode = 0x1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class Client extends EventEmitter {
|
|
23
|
+
socket: internal.Duplex
|
|
24
|
+
connectTime: number;
|
|
25
|
+
constructor(socket: internal.Duplex) {
|
|
26
|
+
super()
|
|
27
|
+
this.socket = socket
|
|
28
|
+
this.connectTime = Date.now()
|
|
29
|
+
}
|
|
30
|
+
createFrame(content: string, options?: { opcode: number; }):Buffer {
|
|
31
|
+
const size = Buffer.byteLength(content);
|
|
32
|
+
let buf
|
|
33
|
+
if (size > sixteenBitsMax) {
|
|
34
|
+
buf = Buffer.alloc(10 + size);
|
|
35
|
+
buf[1] = 127;
|
|
36
|
+
buf.writeUInt32BE(size, 6);
|
|
37
|
+
buf.write(content, 10);
|
|
38
|
+
} else if (size > 125) {
|
|
39
|
+
buf = Buffer.alloc(4 + size);
|
|
40
|
+
buf[1] = 126;
|
|
41
|
+
buf.writeUInt16BE(size, 2);
|
|
42
|
+
buf.write(content, 4);
|
|
43
|
+
} else {
|
|
44
|
+
buf = Buffer.alloc(2 + size);
|
|
45
|
+
buf[1] = size;
|
|
46
|
+
buf.write(content, 2);
|
|
47
|
+
}
|
|
48
|
+
if (options) {
|
|
49
|
+
const { opcode } = options;
|
|
50
|
+
if (opcode && opcode < 15 && opcode >= 0) buf[0] = 128 | opcode;
|
|
51
|
+
}
|
|
52
|
+
else buf[0] = opcodeMap.text
|
|
53
|
+
return buf;
|
|
54
|
+
}
|
|
55
|
+
send(data: string, options?: { opcode: number; }):void {
|
|
56
|
+
this.socket.write(this.createFrame(data, options));
|
|
57
|
+
}
|
|
58
|
+
ping():void {
|
|
59
|
+
this.socket.write(this.createFrame("", { opcode: 9 }));
|
|
60
|
+
}
|
|
61
|
+
pong():void {
|
|
62
|
+
this.socket.write(this.createFrame("", { opcode: 10 }));
|
|
63
|
+
}
|
|
64
|
+
close():void {
|
|
65
|
+
this.socket.write(this.createFrame("", { opcode: 8 }));
|
|
66
|
+
this.socket.destroy();
|
|
67
|
+
this.emit("close");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class WebSocket extends EventEmitter {
|
|
72
|
+
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
73
|
+
clients: Array<Client>
|
|
74
|
+
constructor({ port = 30104 }: { port: number }) {
|
|
75
|
+
super()
|
|
76
|
+
this.server = http.createServer();
|
|
77
|
+
this.server.listen(port);
|
|
78
|
+
this.clients = []
|
|
79
|
+
this.server.on("upgrade", (req, socket) => this.create(req, socket))
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @description: 向所有 client 广播
|
|
83
|
+
* @param {string} data
|
|
84
|
+
*/
|
|
85
|
+
broadcast(data: string): void {
|
|
86
|
+
this.clients.forEach((client) => client.send(data));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* @description: 创建client
|
|
90
|
+
* @param {http} req
|
|
91
|
+
* @param {internal} socket
|
|
92
|
+
*/
|
|
93
|
+
create(req: http.IncomingMessage, socket: internal.Duplex): void {
|
|
94
|
+
socket.write([
|
|
95
|
+
"HTTP/1.1 101 Switching Protocols",
|
|
96
|
+
"Upgrade: websocket",
|
|
97
|
+
"Connection: Upgrade",
|
|
98
|
+
"Sec-WebSocket-Accept: " + crypto.createHash("sha1").update(req.headers['sec-websocket-key'] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64")
|
|
99
|
+
].join("\n") + "\n\n");
|
|
100
|
+
const client = new Client(socket)
|
|
101
|
+
this.addListenCloseClient(client)
|
|
102
|
+
this.clients.push(client);
|
|
103
|
+
this.emit("connect", client);
|
|
104
|
+
|
|
105
|
+
let buf = Buffer.allocUnsafe(0);
|
|
106
|
+
let data = Buffer.allocUnsafe(0);
|
|
107
|
+
/**
|
|
108
|
+
* @description: 处理帧meta
|
|
109
|
+
* @param {FrameMeta} meta
|
|
110
|
+
*/
|
|
111
|
+
const handleFrameMeta = (meta: FrameMeta) => {
|
|
112
|
+
const metaLength = meta.metaSize
|
|
113
|
+
buf.subarray(0, metaLength)
|
|
114
|
+
buf = buf.subarray(metaLength);
|
|
115
|
+
return buf
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* @description: 处理帧内容
|
|
119
|
+
* @param {FrameMeta} meta
|
|
120
|
+
*/
|
|
121
|
+
const handleFrameContent = (meta: FrameMeta) => {
|
|
122
|
+
const size = meta.size
|
|
123
|
+
const buffer = buf.subarray(0, size)
|
|
124
|
+
data = Buffer.concat([data, this.iMask(buffer, meta.maskKey)]);
|
|
125
|
+
if (meta.fin) {
|
|
126
|
+
if (meta.opcode === 8) return client.close();
|
|
127
|
+
if (meta.opcode === 9) return client.pong();
|
|
128
|
+
client.emit("data", data);
|
|
129
|
+
data = Buffer.allocUnsafe(0);
|
|
130
|
+
}
|
|
131
|
+
buf = buf.subarray(size);
|
|
132
|
+
}
|
|
133
|
+
socket.on("data", (chunk) => {
|
|
134
|
+
buf = Buffer.concat([buf, chunk]);
|
|
135
|
+
const meta = this.parseFrameMeta(buf);
|
|
136
|
+
handleFrameMeta(meta)
|
|
137
|
+
handleFrameContent(meta)
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
socket.on("end", () => {
|
|
141
|
+
client.close();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
socket.on("error", (err) => {
|
|
145
|
+
client.emit("error", err);
|
|
146
|
+
client.close();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @description: 增加 client 监听关闭事件
|
|
151
|
+
* @param {Client} client
|
|
152
|
+
*/
|
|
153
|
+
addListenCloseClient(client: Client): void {
|
|
154
|
+
client.on('close', () => {
|
|
155
|
+
const index = this.clients.indexOf(client);
|
|
156
|
+
if (index === -1) return false;
|
|
157
|
+
this.clients.splice(index, 1);
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* @description: 解析帧meta
|
|
162
|
+
* @param {Buffer} source
|
|
163
|
+
* @return {FrameMeta}
|
|
164
|
+
*/
|
|
165
|
+
parseFrameMeta(source: Buffer): FrameMeta {
|
|
166
|
+
const src = Buffer.from(source);
|
|
167
|
+
const payloadSize = src[1] & 127;
|
|
168
|
+
let size = 0;
|
|
169
|
+
let metaSize = 0;
|
|
170
|
+
const masked = src[1] >= 128;
|
|
171
|
+
if (payloadSize === 127) {
|
|
172
|
+
size = src.readUInt32BE(6);
|
|
173
|
+
metaSize = 10;
|
|
174
|
+
} else if (payloadSize === 126) {
|
|
175
|
+
size = src.readUInt16BE(2);
|
|
176
|
+
metaSize = 4;
|
|
177
|
+
} else {
|
|
178
|
+
size = payloadSize;
|
|
179
|
+
metaSize = 2;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
fin: src[0] >= 128,
|
|
183
|
+
opcode: src[0] & 15,
|
|
184
|
+
mask: masked,
|
|
185
|
+
maskKey: masked ? src.subarray(metaSize, metaSize + (masked ? 4 : 0)) : null,
|
|
186
|
+
payloadSize,
|
|
187
|
+
size,
|
|
188
|
+
metaSize: metaSize + (masked ? 4 : 0)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
iMask(data: Buffer, key: Buffer | null): Buffer {
|
|
192
|
+
if (!key) return data;
|
|
193
|
+
const buffer = Buffer.from(data);
|
|
194
|
+
for (let i = 0; i < buffer.length; ++i) buffer[i] = buffer[i] ^ key[i % 4];
|
|
195
|
+
return buffer;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
export default WebSocket
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description: 冒泡排序,两两比较,交换位置,遍历n^2
|
|
3
|
+
* @param {Array} list
|
|
4
|
+
* @return {Array}
|
|
5
|
+
*/
|
|
6
|
+
const bubble = (list: Array<number>): Array<number> => {
|
|
7
|
+
const length = list.length
|
|
8
|
+
for (let i = 0; i < length; i++) {
|
|
9
|
+
for (let j = 0; j < length; j++) {
|
|
10
|
+
let temp
|
|
11
|
+
if (list[j] > list[i]) {
|
|
12
|
+
temp = list[j]
|
|
13
|
+
list[j] = list[i]
|
|
14
|
+
list[i] = temp
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return list
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default bubble
|