Add local web server
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"description": "家长学情是基于 uni-app x Vapor 模式开发的移动端应用。当前实现家长账号的注册、密码登录、验证码登录和密码重置流程。",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"serve:web": "node serve-web.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
+157
@@ -0,0 +1,157 @@
|
||||
const http = require('http')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { URL } = require('url')
|
||||
|
||||
const projectRoot = __dirname
|
||||
const defaultWebRoot = path.join(projectRoot, 'unpackage', 'dist', 'build', 'web')
|
||||
const defaultHost = '127.0.0.1'
|
||||
const defaultPort = 5500
|
||||
|
||||
const mimeTypes = {
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.gif': 'image/gif',
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.ico': 'image/x-icon',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.map': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ttf': 'font/ttf',
|
||||
'.txt': 'text/plain; charset=utf-8',
|
||||
'.webp': 'image/webp',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2'
|
||||
}
|
||||
|
||||
function getOption(name, fallback) {
|
||||
const prefix = `${name}=`
|
||||
const argument = process.argv.find((item) => item.startsWith(prefix))
|
||||
return argument ? argument.slice(prefix.length) : fallback
|
||||
}
|
||||
|
||||
function getPort() {
|
||||
const value = getOption('--port', process.env.PORT || String(defaultPort))
|
||||
const port = Number(value)
|
||||
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
||||
throw new Error(`端口无效:${value}`)
|
||||
}
|
||||
|
||||
return port
|
||||
}
|
||||
|
||||
function getWebRoot() {
|
||||
const configuredRoot = getOption('--dir', defaultWebRoot)
|
||||
return path.resolve(projectRoot, configuredRoot)
|
||||
}
|
||||
|
||||
function isInsideRoot(root, target) {
|
||||
const relativePath = path.relative(root, target)
|
||||
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath))
|
||||
}
|
||||
|
||||
function sendText(response, statusCode, message) {
|
||||
response.writeHead(statusCode, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Cache-Control': 'no-store'
|
||||
})
|
||||
response.end(message)
|
||||
}
|
||||
|
||||
function sendFile(response, filePath) {
|
||||
const extension = path.extname(filePath).toLowerCase()
|
||||
const contentType = mimeTypes[extension] || 'application/octet-stream'
|
||||
|
||||
response.writeHead(200, {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': extension === '.html' ? 'no-cache' : 'public, max-age=3600'
|
||||
})
|
||||
fs.createReadStream(filePath).pipe(response)
|
||||
}
|
||||
|
||||
function tryServeFile(response, webRoot, pathname) {
|
||||
const decodedPath = decodeURIComponent(pathname)
|
||||
const relativePath = decodedPath === '/' ? 'index.html' : decodedPath.slice(1)
|
||||
const filePath = path.resolve(webRoot, relativePath)
|
||||
|
||||
if (!isInsideRoot(webRoot, filePath)) {
|
||||
sendText(response, 403, 'Forbidden')
|
||||
return true
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
||||
return false
|
||||
}
|
||||
|
||||
sendFile(response, filePath)
|
||||
return true
|
||||
}
|
||||
|
||||
function createServer(webRoot) {
|
||||
return http.createServer((request, response) => {
|
||||
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
||||
sendText(response, 405, 'Method Not Allowed')
|
||||
return
|
||||
}
|
||||
|
||||
let requestUrl
|
||||
try {
|
||||
requestUrl = new URL(request.url, 'http://localhost')
|
||||
} catch {
|
||||
sendText(response, 400, 'Bad Request')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (tryServeFile(response, webRoot, requestUrl.pathname)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Web 发行项目使用前端路由时,未知路径回退到入口页面。
|
||||
if (!path.extname(requestUrl.pathname) && tryServeFile(response, webRoot, '/index.html')) {
|
||||
return
|
||||
}
|
||||
|
||||
sendText(response, 404, 'Not Found')
|
||||
} catch (error) {
|
||||
console.error(`[serve-web] ${error.message}`)
|
||||
sendText(response, 500, 'Internal Server Error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function printUsage() {
|
||||
console.log('用法:node serve-web.js [--port=5500] [--host=127.0.0.1] [--dir=unpackage/dist/build/web]')
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
||||
printUsage()
|
||||
return
|
||||
}
|
||||
|
||||
const webRoot = getWebRoot()
|
||||
const host = getOption('--host', process.env.HOST || defaultHost)
|
||||
const port = getPort()
|
||||
|
||||
if (!fs.existsSync(webRoot)) {
|
||||
console.error(`未找到 Web 文件目录:${webRoot}`)
|
||||
console.error('请先在 HBuilderX 中发行 Web,或使用 --dir 指定已打包的目录。')
|
||||
process.exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
const server = createServer(webRoot)
|
||||
server.listen(port, host, () => {
|
||||
console.log(`Web 文件目录:${webRoot}`)
|
||||
console.log(`本机访问:http://localhost:${port}`)
|
||||
console.log(`局域网访问:http://<本机IP>:${port}`)
|
||||
console.log('按 Ctrl+C 停止服务器')
|
||||
})
|
||||
}
|
||||
|
||||
start()
|
||||
Reference in New Issue
Block a user