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

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

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

[[257314]]

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) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.

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/, ''

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

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 <= indexreturn 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}`, () => {}) 
}) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.

 

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

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

2022-06-05 13:52:32

Node.jsDNS 的原理DNS 服务器

2021-09-02 10:49:25

Node.jsPHP服务器开发

2011-07-26 11:07:08

JavaScript

2023-01-10 14:11:26

2019-08-29 10:58:02

Web 开发框架

2022-03-08 15:13:34

Fetch APINode.js开发者

2011-09-08 10:21:50

Node.js

2022-09-04 15:54:10

Node.jsAPI技巧

2012-03-07 14:32:41

Node.js

2023-08-29 09:43:21

Node.js.env

2011-10-19 14:38:46

Node.js

2013-11-01 09:34:56

Node.js技术

2016-10-11 12:45:50

PythonWeb服务器

2016-08-10 16:28:00

WebURLHTTP

2016-08-22 20:37:10

PythonWeb服务器
点赞
收藏

51CTO技术栈公众号