欢迎光临
我们一直在努力

Node.js 中怎么利用原生API 搭建一个Web 服务器

这期内容当中小编将会给大家带来有关Node.js 中怎么利用原生API 搭建一个Web 服务器,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

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('<h2>404 Not Found</h2>')             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('<h2>500 Server Error</h2>')           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('<h2>500 Server Error</h2>')   })   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('<h2>404 Not Found</h2>')             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('<h2>500 Server Error</h2>')           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('<h2>500 Server Error</h2>')   })   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 服务器了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注云搜网行业资讯频道。

赞(0)
【声明】:本博客不参与任何交易,也非中介,仅记录个人感兴趣的主机测评结果和优惠活动,内容均不作直接、间接、法定、约定的保证。访问本博客请务必遵守有关互联网的相关法律、规定与规则。一旦您访问本博客,即表示您已经知晓并接受了此声明通告。