网钛远程桌面管理助手
当前位置:首页 > 服务器资讯

服务器资讯

Node.js 原生 api 搭建 web 服务器

时间:2019-02-15 16:51:19   作者:   来源:   阅读:182   评论:0
内容摘要: 【51CTO技术沙龙】10月27日,让我们共同探索AI场景化应用实现之道 --> node.js 实现一个简单的 web 服务器还是比较简单的,以前利用 express 框架实现过『nodeJS搭一个简单的(代理)web服务器』。代码量很少,可是使用时需要安装依赖,......
【51CTO技术沙龙】10月27日,让我们共同探索AI场景化应用实现之道 -->

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

1、静态 web 服务器

  1. 'use strict' 
  2.  
  3. const http = require('http'
  4. const url = require('url'
  5. const fs = require('fs'
  6. const path = require('path'
  7. const cp = require('child_process'
  8.  
  9. const port = 8080 
  10. const hostname = 'localhost' 
  11.  
  12. // 创建 http 服务 
  13. let httpServer = http.createServer(processStatic) 
  14. // 设置监听端口 
  15. httpServer.listen(port hostname () => {   
  16.   console.log(`app is running at port:${port}`)   
  17.   console.log(`url: http://${hostname}:${port}`) 
  18.   cp.exec(`explorer http://${hostname}:${port}` () => {}) 
  19. }) 
  20. // 处理静态资源 
  21. function processStatic(req res) {   
  22.   const mime = { 
  23.     css: 'text/css' 
  24.     gif: 'image/gif' 
  25.     html: 'text/html' 
  26.     ico: 'image/x-icon' 
  27.     jpeg: 'image/jpeg' 
  28.     jpg: 'image/jpeg' 
  29.     js: 'text/javascript' 
  30.     json: 'application/json' 
  31.     pdf: 'application/pdf' 
  32.     png: 'image/png' 
  33.     svg: 'image/svg+xml' 
  34.     woff: 'application/x-font-woff' 
  35.     woff2: 'application/x-font-woff' 
  36.     swf: 'application/x-shockwave-flash' 
  37.     tiff: 'image/tiff' 
  38.     txt: 'text/plain' 
  39.     wav: 'audio/x-wav' 
  40.     wma: 'audio/x-ms-wma' 
  41.     wmv: 'video/x-ms-wmv' 
  42.     xml: 'text/xml' 
  43.   }   
  44.   const requestUrl = req.url   
  45.   let pathName = url.parse(requestUrl).pathname   
  46.   // 中文乱码处理 
  47.   pathName = decodeURI(pathName)   
  48.   let ext = path.extname(pathName)   
  49.   // 特殊 url 处理 
  50.   if (!pathName.endsWith('/') && ext === '' && !requestUrl.includes('?')) { 
  51.     pathName += '/' 
  52.     const redirect = `http://${req.headers.host}${pathName}` 
  53.     redirectUrl(redirect res) 
  54.   }   
  55.   // 解释 url 对应的资源文件路径 
  56.   let filePath = path.resolve(__dirname + pathName)   
  57.   // 设置 mime  
  58.   ext = ext ? ext.slice(1) : 'unknown' 
  59.   const contentType = mime[ext] || 'text/plain' 
  60.  
  61.   // 处理资源文件 
  62.   fs.stat(filePath (err stats) => {     
  63.     if (err) { 
  64.       res.writeHead(404 { 'content-type''text/html;charset=utf-8' }) 
  65.       res.end('<h1>404 Not Found</h1>')       
  66.       return 
  67.     }     
  68.     // 处理文件 
  69.     if (stats.isFile()) { 
  70.       readFile(filePath contentType res) 
  71.     }     
  72.     // 处理目录 
  73.     if (stats.isDirectory()) {       
  74.       let html = "<head><meta charset = 'utf-8'/></head><body><ul>" 
  75.       // 遍历文件目录,以超链接返回,方便用户选择 
  76.       fs.readdir(filePath (err files) => {         
  77.         if (err) { 
  78.           res.writeHead(500 { 'content-type': contentType }) 
  79.           res.end('<h1>500 Server Error</h1>'
  80.           return 
  81.         } else {           
  82.           for (let file of files) {             
  83.             if (file === 'index.html') {               
  84.               const redirect = `http://${req.headers.host}${pathName}index.html` 
  85.               redirectUrl(redirect res) 
  86.             } 
  87.             html += `<li><a href='${file}'>${file}</a></li>` 
  88.           } 
  89.           html += '</ul></body>' 
  90.           res.writeHead(200 { 'content-type''text/html' }) 
  91.           res.end(html) 
  92.         } 
  93.       }) 
  94.     } 
  95.   }) 
  96. // 重定向处理 
  97. function redirectUrl(url res) { 
  98.   url = encodeURI(url) 
  99.   res.writeHead(302 { 
  100.     location: url 
  101.   }) 
  102.   res.end() 
  103. // 文件读取 
  104. function readFile(filePath contentType res) { 
  105.   res.writeHead(200 { 'content-type': contentType }) 
  106.   const stream = fs.createReadStream(filePath) 
  107.   stream.on('error' function() { 
  108.     res.writeHead(500 { 'content-type': contentType }) 
  109.     res.end('<h1>500 Server Error</h1>'
  110.   }) 
  111.   stream.pipe(res) 

2、代理功能

  1. // 代理列表 
  2. const proxyTable = { 
  3.   '/api': { 
  4.     target: 'http://127.0.0.1:8090/api' 
  5.     changeOrigin: true 
  6.   } 
  7. // 处理代理列表 
  8. function processProxy(req res) {   
  9.   const requestUrl = req.url   
  10.   const proxy = object.keys(proxyTable)   
  11.   let not_found = true 
  12.   for (let index = 0; index < proxy.length; index++) {     
  13.       const k = proxy[index]     
  14.       const i = requestUrl.indexOf(k)     
  15.       if (i >= 0) { 
  16.         not_found = false 
  17.         const element = proxyTable[k]       
  18.         const newUrl = element.target + requestUrl.slice(i + k.length)       
  19.         if (requestUrl !== newUrl) {        
  20.           const u = url.parse(newUrl true)         
  21.           const options = { 
  22.             hostname : u.hostname  
  23.             port     : u.port || 80 
  24.             path     : u.path        
  25.             method   : req.method 
  26.             headers  : req.headers 
  27.             timeout  : 6000 
  28.           }         
  29.           if(element.changeOrigin){ 
  30.             options.headers['host'] = u.hostname + ':' + ( u.port || 80) 
  31.           }         
  32.           const request = http 
  33.           .request(options response => {             
  34.             // COOKIE 处理 
  35.             if(element.changeOrigin && response.headers['set-COOKIE']){ 
  36.               response.headers['set-COOKIE'] = getHeaderOverride(response.headers['set-COOKIE']) 
  37.             } 
  38.             res.writeHead(response.statusCode response.headers) 
  39.             response.pipe(res) 
  40.           }) 
  41.           .on('error' err => {            
  42.             res.statusCode = 503 
  43.             res.end() 
  44.           }) 
  45.         req.pipe(request) 
  46.       }       
  47.       break 
  48.     } 
  49.   }   
  50.   return not_found 
  51. function getHeaderOverride(value){   
  52.   if (Array.isArray(value)) {       
  53.    for (var i = 0; i < value.length; i++ ) { 
  54.      value[i] = replaceDomain(value[i]) 
  55.    } 
  56.   } else { 
  57.     value = replaceDomain(value) 
  58.   }   
  59.   return value 
  60. function replaceDomain(value) {   
  61.   return value.replace(/domain=[a-z.]*;/'domain=.localhost;').replace(/secure/ ''

3、完整版

服务器接收到 http 请求,首先处理代理列表 proxyTable,然后再处理静态资源。虽然这里面只有二个步骤,但如果按照先后顺序编码,这种方式显然不够灵活,不利于以后功能的扩展。koa 框架的中间件就是一个很好的解决方案。完整代码如下:

  1. 'use strict' 
  2.  
  3. const http = require('http'
  4. const url = require('url'
  5. const fs = require('fs'
  6. const path = require('path'
  7. const cp = require('child_process'
  8. // 处理静态资源 
  9. function processStatic(req res) {   
  10.   const mime = { 
  11.     css: 'text/css' 
  12.     gif: 'image/gif' 
  13.     html: 'text/html' 
  14.     ico: 'image/x-icon' 
  15.     jpeg: 'image/jpeg' 
  16.     jpg: 'image/jpeg' 
  17.     js: 'text/javascript' 
  18.     json: 'application/json' 
  19.     pdf: 'application/pdf' 
  20.     png: 'image/png' 
  21.     svg: 'image/svg+xml' 
  22.     woff: 'application/x-font-woff' 
  23.     woff2: 'application/x-font-woff' 
  24.     swf: 'application/x-shockwave-flash' 
  25.     tiff: 'image/tiff' 
  26.     txt: 'text/plain' 
  27.     wav: 'audio/x-wav' 
  28.     wma: 'audio/x-ms-wma' 
  29.     wmv: 'video/x-ms-wmv' 
  30.     xml: 'text/xml' 
  31.   }   
  32.   const requestUrl = req.url   
  33.   let pathName = url.parse(requestUrl).pathname   
  34.   // 中文乱码处理 
  35.   pathName = decodeURI(pathName)   
  36.   let ext = path.extname(pathName)   
  37.   // 特殊 url 处理 
  38.   if (!pathName.endsWith('/') && ext === '' && !requestUrl.includes('?')) { 
  39.     pathName += '/' 
  40.     const redirect = `http://${req.headers.host}${pathName}` 
  41.     redirectUrl(redirect res) 
  42.   }   
  43.   // 解释 url 对应的资源文件路径 
  44.   let filePath = path.resolve(__dirname + pathName)   
  45.   // 设置 mime  
  46.   ext = ext ? ext.slice(1) : 'unknown' 
  47.   const contentType = mime[ext] || 'text/plain' 
  48.  
  49.   // 处理资源文件 
  50.   fs.stat(filePath (err stats) => {     
  51.    if (err) { 
  52.       res.writeHead(404 { 'content-type''text/html;charset=utf-8' }) 
  53.       res.end('<h1>404 Not Found</h1>')       
  54.       return 
  55.     }    // 处理文件 
  56.     if (stats.isFile()) { 
  57.       readFile(filePath contentType res) 
  58.     }    // 处理目录 
  59.     if (stats.isDirectory()) {       
  60.       let html = "<head><meta charset = 'utf-8'/></head><body><ul>" 
  61.       // 遍历文件目录,以超链接返回,方便用户选择 
  62.       fs.readdir(filePath (err files) => {         
  63.         if (err) { 
  64.           res.writeHead(500 { 'content-type': contentType }) 
  65.           res.end('<h1>500 Server Error</h1>'
  66.           return 
  67.         } else {          
  68.            for (let file of files) {             
  69.             if (file === 'index.html') {              
  70.               const redirect = `http://${req.headers.host}${pathName}index.html` 
  71.               redirectUrl(redirect res) 
  72.             } 
  73.             html += `<li><a href='${file}'>${file}</a></li>` 
  74.           } 
  75.           html += '</ul></body>' 
  76.           res.writeHead(200 { 'content-type''text/html' }) 
  77.           res.end(html) 
  78.         } 
  79.       }) 
  80.     } 
  81.   }) 
  82. // 重定向处理 
  83. function redirectUrl(url res) { 
  84.   url = encodeURI(url) 
  85.   res.writeHead(302 { 
  86.     location: url 
  87.   }) 
  88.   res.end() 
  89. // 文件读取 
  90. function readFile(filePath contentType res) { 
  91.   res.writeHead(200 { 'content-type': contentType }) 
  92.   const stream = fs.createReadStream(filePath) 
  93.   stream.on('error' function() { 
  94.     res.writeHead(500 { 'content-type': contentType }) 
  95.     res.end('<h1>500 Server Error</h1>'
  96.   }) 
  97.   stream.pipe(res) 
  98. // 处理代理列表 
  99. function processProxy(req res) { 
  100.   const requestUrl = req.url 
  101.   const proxy = object.keys(proxyTable) 
  102.   let not_found = true 
  103.   for (let index = 0; index < proxy.length; index++) {     
  104.     const k = proxy[index]     
  105.     const i = requestUrl.indexOf(k)     
  106.     if (i >= 0) { 
  107.       not_found = false 
  108.       const element = proxyTable[k] 
  109.       const newUrl = element.target + requestUrl.slice(i + k.length) 
  110.  
  111.       if (requestUrl !== newUrl) { 
  112.         const u = url.parse(newUrl true
  113.         const options = { 
  114.           hostname : u.hostname  
  115.           port     : u.port || 80 
  116.           path     : u.path        
  117.           method   : req.method 
  118.           headers  : req.headers 
  119.           timeout  : 6000 
  120.         }; 
  121.         if(element.changeOrigin){ 
  122.           options.headers['host'] = u.hostname + ':' + ( u.port || 80) 
  123.         } 
  124.         const request =  
  125.           http.request(options response => {                 
  126.             // COOKIE 处理 
  127.             if(element.changeOrigin && response.headers['set-COOKIE']){ 
  128.               response.headers['set-COOKIE'] = getHeaderOverride(response.headers['set-COOKIE']) 
  129.             } 
  130.             res.writeHead(response.statusCode response.headers) 
  131.             response.pipe(res) 
  132.           }) 
  133.           .on('error' err => { 
  134.             res.statusCode = 503 
  135.             res.end() 
  136.           }) 
  137.         req.pipe(request) 
  138.       } 
  139.       break 
  140.     } 
  141.   } 
  142.   return not_found 
  143. function getHeaderOverride(value){ 
  144.   if (Array.isArray(value)) { 
  145.       for (var i = 0; i < value.length; i++ ) {         
  146.          value[i] = replaceDomain(value[i]) 
  147.       } 
  148.   } else { 
  149.       value = replaceDomain(value) 
  150.   } 
  151.   return value} 
  152. function replaceDomain(value) { 
  153.   return value.replace(/domain=[a-z.]*;/'domain=.localhost;').replace(/secure/ ''
  154. function compose (middleware) { 
  155.   if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')   
  156.   for (const fn of middleware) {     
  157.     if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!'
  158.   }   
  159.   return function (context next) { 
  160.     // 记录上一次执行中间件的位置     
  161.     let index = -1 
  162.     return dispatch(0)    
  163.     function dispatch (i) { 
  164.       // 理论上 i 会大于 index,因为每次执行一次都会把 i递增, 
  165.       // 如果相等或者小于,则说明next()执行了多次      
  166.       if (i <= indexreturn Promise.reject(new Error('next() called multiple times'))       
  167.       index = i 
  168.       let fn = middleware[i]       
  169.       if (i === middleware.length) fn = next 
  170.       if (!fn) return Promise.resolve()      
  171.       try {        
  172.         return Promise.resolve(fn(context function next () {   
  173.            return dispatch(i + 1) 
  174.         })) 
  175.       } catch (err) {         
  176.          return Promise.reject(err) 
  177.       } 
  178.     } 
  179.   } 
  180. function Router(){   
  181.   this.middleware = [] 
  182. Router.prototype.use = function (fn){   
  183.   if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'
  184.   this.middleware.push(fn)  
  185.   return this} 
  186. Router.prototype.callback= function() {   
  187.   const fn = compose(this.middleware)   
  188.   const handleRequest = (req res) => { 
  189.     const ctx = {req res} 
  190.     return this.handleRequest(ctx fn) 
  191.   } 
  192.   return handleRequest 
  193. Router.prototype.handleRequest= function(ctx fn) { 
  194.   fn(ctx) 
  195.  
  196. // 代理列表 
  197. const proxyTable = { 
  198.   '/api': { 
  199.     target: 'http://127.0.0.1:8090/api' 
  200.     changeOrigin: true 
  201.   } 
  202.  
  203. const port = 8080 
  204. const hostname = 'localhost' 
  205. const appRouter = new Router() 
  206.  
  207. // 使用中间件 
  208. appRouter.use(async(ctxnext)=>{ 
  209.   if(processProxy(ctx.req ctx.res)){ 
  210.     next() 
  211.   } 
  212. }).use(async(ctx)=>{ 
  213.   processStatic(ctx.req ctx.res) 
  214. }) 
  215.  
  216. // 创建 http 服务 
  217. let httpServer = http.createServer(appRouter.callback()) 
  218.  
  219. // 设置监听端口 
  220. httpServer.listen(port hostname () => { 
  221.   console.log(`app is running at port:${port}`) 
  222.   console.log(`url: http://${hostname}:${port}`) 
  223.   cp.exec(`explorer http://${hostname}:${port}` () => {}) 
  224. }) 

标签:处理  pathname  res  gt  writeHead  

相关文章

相关评论

该网站程序来至《网钛CMS PHP版》  企业客服QQ:800166366    OTCMS作者QQ:877873666    阿里旺旺:    闽ICP备12010380号