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')
|
2024-01-02 17:11:14 +08:00
|
|
|
const { join, basename } = require('path')
|
2022-04-09 20:26:23 +08:00
|
|
|
const { parse } = require('url')
|
|
|
|
const http = require('http')
|
2024-02-21 14:52:53 +08:00
|
|
|
const http2 = require('http2')
|
2022-04-09 20:26:23 +08:00
|
|
|
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 decode = decodeURIComponent
|
2024-01-02 17:11:14 +08:00
|
|
|
const statItem = vsc.window.createStatusBarItem(vsc.StatusBarAlignment.Left, 0)
|
2022-04-09 20:26:23 +08:00
|
|
|
|
|
|
|
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'
|
|
|
|
}
|
|
|
|
|
2024-02-21 14:52:53 +08:00
|
|
|
const SERVER_OPTIONS = { allowHTTP1: true }
|
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'
|
|
|
|
}
|
|
|
|
|
2024-01-02 17:11:14 +08:00
|
|
|
const CONFIG_FILE = '.httpserver'
|
|
|
|
|
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
|
2024-01-02 17:11:14 +08:00
|
|
|
let server
|
2023-04-17 11:33:48 +08:00
|
|
|
let ws = null
|
2024-02-21 14:52:53 +08:00
|
|
|
let domain
|
|
|
|
let isHttps = false
|
2022-04-09 20:26:23 +08:00
|
|
|
|
2023-04-17 11:33:48 +08:00
|
|
|
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) {
|
|
|
|
enabled = false
|
|
|
|
return
|
|
|
|
}
|
2024-01-02 17:11:14 +08:00
|
|
|
console.log(`尝试使用${port}端口...`)
|
|
|
|
|
2024-02-21 14:52:53 +08:00
|
|
|
server = (isHttps ? http2.createSecureServer : http.createServer)(
|
|
|
|
isHttps ? SERVER_OPTIONS : void 0
|
|
|
|
)
|
|
|
|
|
|
|
|
server
|
|
|
|
.on('request', 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)
|
|
|
|
}
|
|
|
|
|
2024-02-21 14:52:53 +08:00
|
|
|
res.writeHead(200, isHttps ? void 0 : '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))
|
2024-02-21 14:52:53 +08:00
|
|
|
res.writeHead(200, isHttps ? void 0 : 'OK')
|
2023-04-17 11:33:48 +08:00
|
|
|
|
2023-11-14 19:12:43 +08:00
|
|
|
res.end(code)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let indexFile = join(root, 'index.html')
|
|
|
|
if (fs.isfile(indexFile)) {
|
|
|
|
code = fs
|
|
|
|
.cat(indexFile)
|
|
|
|
.toString()
|
|
|
|
.replace('</head>', HMR_SCRIPT + '</head>')
|
|
|
|
|
|
|
|
res.setHeader('accept-ranges', 'bytes')
|
|
|
|
res.setHeader('content-type', MIME_TYPES.html)
|
|
|
|
res.setHeader('content-length', Buffer.byteLength(code))
|
2024-02-21 14:52:53 +08:00
|
|
|
res.writeHead(200, isHttps ? void 0 : 'OK')
|
2023-11-14 19:12:43 +08:00
|
|
|
|
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)
|
2024-02-21 14:52:53 +08:00
|
|
|
res.writeHead(404, isHttps ? void 0 : 'Not Found')
|
2023-11-14 19:12:43 +08:00
|
|
|
res.end(null)
|
2022-04-09 20:26:23 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.on('error', err => {
|
2024-01-02 17:11:14 +08:00
|
|
|
console.log(`${port}端口被占用~~~`)
|
2022-04-09 20:26:23 +08:00
|
|
|
port++
|
|
|
|
createServer()
|
|
|
|
})
|
2024-02-21 14:52:53 +08:00
|
|
|
.on('listening', _ => {
|
|
|
|
baseUrl = `http${isHttps ? 's' : ''}://${domain || '127.0.0.1'}:${port}`
|
|
|
|
ws = new WebSocket(server)
|
|
|
|
statItem.text = `http.server (🟢)`
|
|
|
|
statItem.show()
|
|
|
|
})
|
|
|
|
.listen(port)
|
2023-04-17 11:33:48 +08:00
|
|
|
|
2024-02-21 14:52:53 +08:00
|
|
|
return server
|
2022-04-09 20:26:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function __init__() {
|
|
|
|
let folders = vsc.workspace.workspaceFolders
|
2024-02-21 14:52:53 +08:00
|
|
|
let commonConfig = vsc.workspace.getConfiguration('HttpServer')
|
2022-04-09 20:26:23 +08:00
|
|
|
|
2024-01-02 17:34:57 +08:00
|
|
|
enabled = false
|
|
|
|
|
2024-01-02 17:11:14 +08:00
|
|
|
statItem.hide()
|
|
|
|
|
2022-04-09 20:26:23 +08:00
|
|
|
if (folders && folders.length) {
|
|
|
|
root = folders[0].uri.fsPath
|
|
|
|
}
|
2024-01-02 17:11:14 +08:00
|
|
|
|
|
|
|
if (server) {
|
|
|
|
server.close()
|
|
|
|
server.closeAllConnections()
|
|
|
|
server = null
|
|
|
|
}
|
|
|
|
|
2022-04-09 20:26:23 +08:00
|
|
|
if (root) {
|
2024-01-02 17:11:14 +08:00
|
|
|
let file = join(root, CONFIG_FILE)
|
2022-04-09 20:26:23 +08:00
|
|
|
//
|
|
|
|
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)
|
|
|
|
}
|
2024-02-21 14:52:53 +08:00
|
|
|
isHttps = conf.https || conf.http2
|
2022-04-25 16:57:06 +08:00
|
|
|
|
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]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-21 14:52:53 +08:00
|
|
|
if (isHttps) {
|
|
|
|
let key = conf.key || commonConfig.key
|
|
|
|
let cert = conf.cert || commonConfig.cert
|
|
|
|
|
|
|
|
if (key) {
|
|
|
|
SERVER_OPTIONS.key = fs.cat(key)
|
|
|
|
}
|
|
|
|
if (cert) {
|
|
|
|
SERVER_OPTIONS.cert = fs.cat(cert)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
domain = conf.domain || commonConfig.domain
|
|
|
|
|
2022-04-09 20:26:23 +08:00
|
|
|
if (conf.enabled) {
|
|
|
|
enabled = true
|
|
|
|
port = conf.port || 23333
|
|
|
|
|
|
|
|
createServer()
|
|
|
|
} else {
|
2024-01-02 17:11:14 +08:00
|
|
|
statItem.text = `http.server (🔴)`
|
|
|
|
statItem.show()
|
2022-04-09 20:26:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-02 17:11:14 +08:00
|
|
|
export function deactivate() {}
|
2022-04-09 20:26:23 +08:00
|
|
|
|
2024-01-02 17:11:14 +08:00
|
|
|
export function activate(ctx) {
|
2022-04-09 20:26:23 +08:00
|
|
|
__init__()
|
|
|
|
|
2024-02-21 14:52:53 +08:00
|
|
|
vsc.workspace.onDidChangeConfiguration(__init__)
|
|
|
|
|
2023-04-17 11:33:48 +08:00
|
|
|
vsc.workspace.onDidSaveTextDocument(doc => {
|
2024-01-02 17:34:57 +08:00
|
|
|
let file = doc.fileName
|
|
|
|
if (file?.startsWith(root)) {
|
|
|
|
if (basename(file) === CONFIG_FILE) {
|
|
|
|
return __init__()
|
|
|
|
}
|
|
|
|
if (enabled) {
|
2023-04-17 11:33:48 +08:00
|
|
|
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)
|
|
|
|
}
|