您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關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服務器的案例”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。