内容简介:node.js 实现一个简单的 web 服务器还是比较简单的,以前利用 express 框架实现过『nodeJS搭一个简单的(代理)web服务器』。代码量很少,可是使用时需要安装依赖,多处使用难免有点不方便。于是便有了完全使用原生 api 来重写的想法,也当作一次 node.js 复习。3、完整版
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: http://${hostname}:${port}`) cp.exec(`explorer http://${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('/') && ext === '' && !requestUrl.includes('?')) { pathName += '/' const redirect = `http://${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 = `http://${req.headers.host}${pathName}index.html` redirectUrl(redirect, res) } html += `<li><a href='${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、完整版
服务器接收到 http 请求,首先处理代理列表 proxyTable,然后再处理静态资源。虽然这里面只有二个步骤,但如果按照先后顺序编码,这种方式显然不够灵活,不利于以后功能的扩展。koa 框架的中间件就是一个很好的解决方案。完整代码如下:
'use strict' const http = require('http') const url = require('url') const fs = require('fs') const path = require('path') const cp = require('child_process') // 处理静态资源 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('/') && ext === '' && !requestUrl.includes('?')) { pathName += '/' const redirect = `http://${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 = `http://${req.headers.host}${pathName}index.html` redirectUrl(redirect, res) } html += `<li><a href='${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) } // 处理代理列表 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/, '') } function compose (middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } return function (context, next) { // 记录上一次执行中间件的位置 let index = -1 return dispatch(0) function dispatch (i) { // 理论上 i 会大于 index,因为每次执行一次都会把 i递增, // 如果相等或者小于,则说明next()执行了多次 if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } } function Router(){ this.middleware = [] } Router.prototype.use = function (fn){ if (typeof fn !== 'function') throw new TypeError('middleware must be a function!') this.middleware.push(fn) return this} Router.prototype.callback= function() { const fn = compose(this.middleware) const handleRequest = (req, res) => { const ctx = {req, res} return this.handleRequest(ctx, fn) } return handleRequest } Router.prototype.handleRequest= function(ctx, fn) { fn(ctx) } // 代理列表 const proxyTable = { '/api': { target: 'http://127.0.0.1:8090/api', changeOrigin: true } } const port = 8080 const hostname = 'localhost' const appRouter = new Router() // 使用中间件 appRouter.use(async(ctx,next)=>{ if(processProxy(ctx.req, ctx.res)){ next() } }).use(async(ctx)=>{ processStatic(ctx.req, ctx.res) }) // 创建 http 服务 let httpServer = http.createServer(appRouter.callback()) // 设置监听端口 httpServer.listen(port, hostname, () => { console.log(`app is running at port:${port}`) console.log(`url: http://${hostname}:${port}`) cp.exec(`explorer http://${hostname}:${port}`, () => {}) })
以上所述就是小编给大家介绍的《Node.js 原生 api 搭建 web 服务器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- React Native搭建开发环境/link原生依赖问题
- 零信任原生安全:超越云原生安全
- 畅谈云原生(下):云原生的飞轮理论
- 【云原生丨主题周】云原生为何物?为何重要?
- Micronaut 2.0.0 发布,原生云原生微服务框架
- 2018云原生技术实践峰会(CNBPS) 重新定义云原生
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Hacking Growth
Sean Ellis、Morgan Brown / Crown Business / 2017-4-25 / USD 29.00
The definitive playbook by the pioneers of Growth Hacking, one of the hottest business methodologies in Silicon Valley and beyond. It seems hard to believe today, but there was a time when Airbnb w......一起来看看 《Hacking Growth》 这本书的介绍吧!