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

服务器
node.js 实现一个简单的 web 服务器还是比较简单的,以前利用 express 框架实现过『nodeJS搭一个简单的(代理)web服务器』。代码量很少,可是使用时需要安装依赖,多处使用难免有点不方便。

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

[[257314]]

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(ctx,next)=>{ 
  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. }) 

 

责任编辑:武晓燕 来源: 想不起来了
相关推荐

2020-10-29 16:00:03

Node.jsweb前端

2020-10-12 08:06:28

HTTP 服务器证书

2014-04-21 14:56:45

NodeJSOAuth2服务器

2020-03-17 13:24:04

微服务架构数据

2011-06-17 10:29:04

Nodejavascript

2021-09-02 10:49:25

Node.jsPHP服务器开发

2022-06-05 13:52:32

Node.jsDNS 的原理DNS 服务器

2011-07-26 11:07:08

JavaScript

2023-01-10 14:11:26

2022-03-08 15:13:34

Fetch APINode.js开发者

2019-08-29 10:58:02

Web 开发框架

2011-09-08 10:21:50

Node.js

2022-09-04 15:54:10

Node.jsAPI技巧

2011-10-19 14:38:46

Node.js

2023-08-29 09:43:21

Node.js.env

2012-03-07 14:32:41

Node.js

2016-08-10 16:28:00

WebURLHTTP

2016-08-22 20:37:10

PythonWeb服务器

2016-10-11 12:45:50

PythonWeb服务器

2022-08-28 16:30:34

Node.jsDocker指令
点赞
收藏

51CTO技术栈公众号