node.js 实现一个简单的 web 服务器还是比较简单的,以前利用 express 框架实现过『nodeJS搭一个简单的(代理)web服务器』。代码量很少,可是使用时需要安装依赖,多处使用难免有点不方便。于是便有了完全使用原生 api 来重写的想法,也当作一次 node.js 复习。
1、静态 web 服务器
'use strict' const http = require('http') const url = require('url') const fs = require('fs') const path = require('path') const cp = require('child_process') const port = 8080 const hostname = 'localhost' // 创建 http 服务 let httpServer = http.createServer(processStatic) // 设置监听端口 httpServer.listen(port, hostname, () => { console.log(`app is running at port:${port}`) console.log(`url: ${hostname}:${port}`) cp.exec(`explorer ${hostname}:${port}`, () => {}) }) // 处理静态资源 function processStatic(req, res) { const mime = { css: 'text/css', gif: 'image/gif', html: 'text/html', ico: 'image/x-icon', jpeg: 'image/jpeg', jpg: 'image/jpeg', js: 'text/javascript', json: 'application/json', pdf: 'application/pdf', png: 'image/png', svg: 'image/svg+xml', woff: 'application/x-font-woff', woff2: 'application/x-font-woff', swf: 'application/x-shockwave-flash', tiff: 'image/tiff', txt: 'text/plain', wav: 'audio/x-wav', wma: 'audio/x-ms-wma', wmv: 'video/x-ms-wmv', xml: 'text/xml' } const requestUrl = req.url let pathName = url.parse(requestUrl).pathname // 中文乱码处理 pathName = decodeURI(pathName) let ext = path.extname(pathName) // 特殊 url 处理 if (!pathName.endsWith('https://www.jb51.net/') && ext === '' && !requestUrl.includes('?')) { pathName += 'https://www.jb51.net/' const redirect = `${req.headers.host}${pathName}` redirectUrl(redirect, res) } // 解释 url 对应的资源文件路径 let filePath = path.resolve(__dirname + pathName) // 设置 mime ext = ext ? ext.slice(1) : 'unknown' const contentType = mime[ext] || 'text/plain' // 处理资源文件 fs.stat(filePath, (err, stats) => { if (err) { res.writeHead(404, { 'content-type': 'text/html;charset=utf-8' }) res.end('<h1>404 Not Found</h1>') return } // 处理文件 if (stats.isFile()) { readFile(filePath, contentType, res) } // 处理目录 if (stats.isDirectory()) { let html = "<head><meta charset = 'utf-8'/></head><body><ul>" // 遍历文件目录,以超链接返回,方便用户选择 fs.readdir(filePath, (err, files) => { if (err) { res.writeHead(500, { 'content-type': contentType }) res.end('<h1>500 Server Error</h1>') return } else { for (let file of files) { if (file === 'index.html') { const redirect = `${req.headers.host}${pathName}index.html` redirectUrl(redirect, res) } html += `<li><a href='https://www.jb51.net/${file}'>${file}</a></li>` } html += '</ul></body>' res.writeHead(200, { 'content-type': 'text/html' }) res.end(html) } }) } }) } // 重定向处理 function redirectUrl(url, res) { url = encodeURI(url) res.writeHead(302, { location: url }) res.end() } // 文件读取 function readFile(filePath, contentType, res) { res.writeHead(200, { 'content-type': contentType }) const stream = fs.createReadStream(filePath) stream.on('error', function() { res.writeHead(500, { 'content-type': contentType }) res.end('<h1>500 Server Error</h1>') }) stream.pipe(res) }
2、代理功能
// 代理列表 const proxyTable = { '/api': { target: 'http://127.0.0.1:8090/api', changeOrigin: true } } // 处理代理列表 function processProxy(req, res) { const requestUrl = req.url const proxy = Object.keys(proxyTable) let not_found = true for (let index = 0; index < proxy.length; index++) { const k = proxy[index] const i = requestUrl.indexOf(k) if (i >= 0) { not_found = false const element = proxyTable[k] const newUrl = element.target + requestUrl.slice(i + k.length) if (requestUrl !== newUrl) { const u = url.parse(newUrl, true) const options = { hostname : u.hostname, port : u.port || 80, path : u.path, method : req.method, headers : req.headers, timeout : 6000 } if(element.changeOrigin){ options.headers['host'] = u.hostname + ':' + ( u.port || 80) } const request = http .request(options, response => { // cookie 处理 if(element.changeOrigin && response.headers['set-cookie']){ response.headers['set-cookie'] = getHeaderOverride(response.headers['set-cookie']) } res.writeHead(response.statusCode, response.headers) response.pipe(res) }) .on('error', err => { res.statusCode = 503 res.end() }) req.pipe(request) } break } } return not_found } function getHeaderOverride(value){ if (Array.isArray(value)) { for (var i = 0; i < value.length; i++ ) { value[i] = replaceDomain(value[i]) } } else { value = replaceDomain(value) } return value } function replaceDomain(value) { return value.replace(/domain=[a-z.]*;/,'domain=.localhost;').replace(/secure/, '') }
3、完整版