Node.js 原生 API 搭建 Web 服务器

admin 行业动态 2024-08-27 28

摘要:node.js 实现一个简单的 web 服务器还是比较简单的,以前利用 express 框架实现过『nodeJS搭一个简单的(代理)web服务器』。代码量很少,可是使用时需要安装依赖,多处使用难免有点不方便。于是便有了完全使用原生 api 来重写的想法,也当作一次 node.js 复习。 1、静态 web 服务器...

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}`, () => {}) }) 

相关推荐

评论列表
关闭

用微信“扫一扫”