http.server/index.js

268 lines
6.3 KiB
JavaScript
Raw Normal View History

2022-04-09 20:26:23 +08:00
/**
*
* @author yutent<yutent.io@gmail.com>
* @date 2022/04/09 18:26:35
*/
const vsc = require('vscode')
const { join } = require('path')
const { parse } = require('url')
const http = require('http')
const fs = require('iofs')
2023-04-17 11:33:48 +08:00
const { WebSocketServer } = require('ws')
2022-04-09 20:26:23 +08:00
const std = vsc.window.createOutputChannel('http.server')
const decode = decodeURIComponent
const MIME_TYPES = {
html: 'text/html;charset=utf-8',
txt: 'text/plain;charset=utf-8',
css: 'text/css;charset=utf-8',
xml: 'text/xml;charset=utf-8',
gif: 'image/gif',
jpg: 'image/jpeg',
webp: 'image/webp',
tiff: 'image/tiff',
png: 'image/png',
svg: 'image/svg+xml',
ico: 'image/x-icon',
bmp: 'image/x-ms-bmp',
js: 'application/javascript;charset=utf-8',
json: 'application/json;charset=utf-8',
mp3: 'audio/mpeg',
ogg: 'audio/ogg',
m4a: 'audio/x-m4a',
mp4: 'video/mp4',
webm: 'video/webm',
ttf: 'font/font-ttf',
woff: 'font/font-woff',
woff2: 'font/font-woff2',
2023-02-23 09:45:14 +08:00
wast: 'application/wast',
wasm: 'application/wasm',
2022-04-09 20:26:23 +08:00
other: 'application/octet-stream'
}
2022-04-25 23:37:27 +08:00
const COMMON_HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Cache-Control': 'no-store',
'X-Powered-By': 'VS Code simple.http'
}
2022-04-09 20:26:23 +08:00
MIME_TYPES.htm = MIME_TYPES.html
MIME_TYPES.jpeg = MIME_TYPES.jpg
MIME_TYPES.tif = MIME_TYPES.tiff
let root
let enabled = false
let port = 23333
let baseUrl = 'http://127.0.0.1:' + port
2023-04-17 11:33:48 +08:00
let ws = null
2022-04-09 20:26:23 +08:00
std.out = function (...args) {
std.appendLine('[simple.http]: ' + args.join(' '))
2023-04-17 11:33:48 +08:00
// console.log('[simple.http]: ' + args.join(' '))
}
const HMR_SCRIPT = `
<script>
!(function http_hmr(){
2023-11-14 18:43:50 +08:00
var ws = new WebSocket(\`\${location.protocol === 'https:' ? 'wss' : 'ws'}://\${location.host}/ws-http-hmr\`)
2023-04-17 11:33:48 +08:00
ws.addEventListener('open', function (r) {
if(http_hmr.closed){
delete http_hmr.closed
location.reload()
}
console.log('http.server hmr ready...')
})
ws.addEventListener('close', function(){
http_hmr.closed = true
setTimeout(http_hmr, 2000)
})
ws.addEventListener('message', function (ev) {
location.reload()
})
})()
</script>
`
class WebSocket {
#ws = null // ws实例
#queue = [] // 消息队列
constructor(server) {
if (server.listening) {
let conn = new WebSocketServer({ server, path: '/ws-http-hmr' })
conn.on('connection', ws => {
this.#ws = ws
if (this.#queue.length) {
this.#queue = []
this.send()
}
})
}
}
send(msg = {}) {
if (this.#ws) {
this.#ws.send(JSON.stringify(msg))
} else {
this.#queue.push(msg)
}
}
2022-04-09 20:26:23 +08:00
}
function createServer() {
if (port > 65535) {
std.out('端口超出有效范围0-65535, 当前为: ' + port)
enabled = false
return
}
std.out(`尝试使用${port}端口...`)
2023-04-17 11:33:48 +08:00
const server = http
2022-04-09 20:26:23 +08:00
.createServer(function (req, res) {
2022-04-26 01:55:30 +08:00
let pathname = parse(req.url).pathname.slice(1)
2023-03-01 15:42:09 +08:00
let paths = pathname.split('/')
2022-04-09 20:26:23 +08:00
pathname = decode(pathname) || 'index.html'
2022-04-26 01:46:30 +08:00
if (pathname.endsWith('/')) {
pathname += 'index.html'
}
2022-04-09 20:26:23 +08:00
let file = join(root, pathname)
let stat = fs.stat(file)
let ext = pathname.split('.').pop()
2023-04-17 11:33:48 +08:00
let code
2022-04-09 20:26:23 +08:00
2022-04-25 23:37:27 +08:00
for (let k in COMMON_HEADERS) {
res.setHeader(k, COMMON_HEADERS[k])
}
2022-04-09 20:26:23 +08:00
if (stat.isFile()) {
2022-04-25 23:37:27 +08:00
res.setHeader('accept-ranges', 'bytes')
res.setHeader('content-type', MIME_TYPES[ext] || MIME_TYPES.other)
2023-04-17 11:33:48 +08:00
if (ext === 'html') {
code = fs
.cat(file)
.toString()
.replace('</head>', HMR_SCRIPT + '</head>')
2023-04-28 15:44:40 +08:00
res.setHeader('content-length', Buffer.byteLength(code))
2023-04-17 11:33:48 +08:00
} else {
res.setHeader('content-length', stat.size)
}
2022-04-09 20:26:23 +08:00
res.writeHead(200, 'OK')
2023-04-17 11:33:48 +08:00
if (ext === 'html') {
res.end(code)
} else {
fs.origin.createReadStream(file).pipe(res)
}
2022-04-09 20:26:23 +08:00
} else {
2023-03-01 15:42:09 +08:00
if (paths.length > 1 && paths[0].endsWith('.html')) {
let rootFile = join(root, paths[0])
if (fs.isfile(rootFile)) {
2023-04-17 11:33:48 +08:00
code = fs
.cat(rootFile)
.toString()
.replace('</head>', HMR_SCRIPT + '</head>')
2023-03-01 15:42:09 +08:00
res.setHeader('accept-ranges', 'bytes')
res.setHeader('content-type', MIME_TYPES.html)
2023-04-28 15:44:40 +08:00
res.setHeader('content-length', Buffer.byteLength(code))
2023-03-01 15:42:09 +08:00
res.writeHead(200, 'OK')
2023-04-17 11:33:48 +08:00
res.end(code)
2023-03-01 15:42:09 +08:00
return
}
}
2022-04-25 23:37:27 +08:00
res.setHeader('content-length', 0)
2022-04-26 01:55:30 +08:00
res.setHeader('x-url', file)
2022-04-09 20:26:23 +08:00
res.writeHead(404, 'Not Found')
res.end('')
}
})
.listen(port)
.on('error', err => {
std.out(`${port}端口被占用~~~`)
port++
createServer()
})
2023-04-17 11:33:48 +08:00
server.on('listening', _ => {
baseUrl = 'http://127.0.0.1:' + port
ws = new WebSocket(server)
std.out('启动成功, 请访问', baseUrl)
})
2022-04-09 20:26:23 +08:00
}
function __init__() {
let folders = vsc.workspace.workspaceFolders
if (folders && folders.length) {
root = folders[0].uri.fsPath
}
if (root) {
let file = join(root, '.httpserver')
//
if (fs.isfile(file)) {
let conf = JSON.parse(fs.cat(file).toString())
2022-04-25 16:57:06 +08:00
if (conf.root) {
root = join(root, conf.root)
}
2022-04-25 23:37:27 +08:00
if (conf.headers) {
for (let k in conf.headers) {
let _k = k.toLowerCase()
2022-04-25 23:53:24 +08:00
if (['accept-ranges', 'content-type', 'content-length'].includes(_k)) {
2022-04-25 23:37:27 +08:00
continue
}
COMMON_HEADERS[_k] = conf.headers[k]
}
}
2022-04-09 20:26:23 +08:00
if (conf.enabled) {
enabled = true
port = conf.port || 23333
createServer()
} else {
std.out('发现配置文件, 但服务为 关闭状态')
}
}
}
}
function deactivate() {}
exports.activate = function (ctx) {
__init__()
2023-04-17 11:33:48 +08:00
vsc.workspace.onDidSaveTextDocument(doc => {
if (enabled) {
let file = doc.fileName
if (file.startsWith(root)) {
ws.send()
}
}
})
2022-04-09 20:26:23 +08:00
let cmd = vsc.commands.registerCommand('HttpServer.open', _ => {
if (enabled) {
let editor = vsc.window.activeTextEditor
if (editor) {
let pathname = editor.document.uri.fsPath.slice(root.length)
vsc.commands.executeCommand('vscode.open', baseUrl + pathname)
}
}
})
ctx.subscriptions.push(cmd)
}
exports.deactivate = deactivate