接下來請深呼吸一大片代碼正奔涌而來,該項目託管在https://github.com/MaxMin-she... 請各位同仁大神view指導。
一、route文件用來路由不一樣的actioncss
const DNSserver = require('./src/controller/DNSserver.js'); const Staticfiels = require('./src/controller/Staticfiels.js'); const SaveImg = require('./src/controller/Saveimg.js') exports.getRouter = function (req, res) { console.log(url.parse(req.url)); const pathname = url.parse(req.url).pathname; switch (pathname) { case '/dns': DNSserver.parse(req, res); break; case '': case '/': case '/index': Staticfiels.index(req, res); break; case '/post/img': SaveImg.saveimg (req, res); break; default: Staticfiels.loadfiels(req, res, pathname); } } module.exports = exports;
DNS服務器的有三個功能因此包含有三個模塊兒,開頭就引入了三個模塊兒,經過請求的url路徑名稱咱們路由到不一樣的處理模塊兒。這個簡易的DNS服務器總共有四個自定義的模塊:html
接下來,咱們分別來介紹這幾個模塊兒的功能和做用:git
/** * handdle error * @param err * @param msg */ exports.errorHandle = function(err, type){ const time = new Date(); console.log(`------------------------\n time: ${time}\n err: ${err}\n type: ${type}\n ------------------------\n `); } module.exports = exports;
該模塊兒的做用是經過傳入的err,和提示的msg將錯誤的結果打印出來github
/** * DNS解析 */ const url = require('url'); const querystring = require('querystring'); const dns = require('dns'); const util = require('../utile/utile.js'); /** * @param req * @param res */ exports.parse = function(req, res){ const query_url = url.parse(req.url); const query = querystring.parse(query_url.query); dns.resolve4(query['hostname'], function(err, addresses){ if(err){ util.errorHandle(err, 'DNS failed'); res.writeHead(400); res.end(); } else { res.writeHead(200); res.end(addresses.toString()); } }); } module.exports = exports;
假設咱們的請求是:‘http://localhost:3000/dns?hostname=www.google.com’json
/** * get static files */ const fs = require('fs'); const path = require('path'); const util = require('../utile/utile.js'); /** * read Fiels * @param req * @param res * @param pathname */ const readStaticFiles = function(req, res, filename){ fs.readFile(filename, function(err, data){ if(err){ util.errorHandle(err, 'filed readFile'); res.writeHead(404); res.end('We Got A Problem: File Not Found'); } else { res.writeHead(200); res.end(data); } }) } /** * exports function of reading files */ exports.loadfiels = function(req, res, pathname){ const filename = path.join('E:\static', pathname); console.log(filename); readStaticFiles(req, res, filename); } module.exports = exports; /** * exports function of getting default page */ exports.index = function(req, res){ const filename = path.join('\static', 'html/index.html'); readStaticFiles(req, res, filename); }
Staticfiles文件中有兩個輸出,index模塊是用來處理沒有輸入文件名時的默認值,loadfiels模塊則能夠根據文件名返回靜態文件,兩個模塊兒都使用的同一函數readStaticFiles進行文件的讀取操做。數組
在這個模塊中咱們將實現圖片上傳下載的功能。
首先在html中完成一個form表單:服務器
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="/css/index.css" /> </head> <body> <form enctype="multipart/form-data" action="post/img" method="POST"> <input name="userfile1" type="file"> <input type="submit" value="發送文件"> </form> </body> </html>
'multipart/form-data'是post的一種數據提交方式,用於附件的上傳,表單中還有file類型的控件,用於上傳一張圖片:網絡
接下來,咱們瞭解一下請求報文頭和報文體的格式和內容:app
在請求報文頭中能夠找到這些信息,其中Content-Type中的boundary屬性很重要,由於附件的數據量比較大,因此一個附件須要多部分提交才能完成,而boundary就是每一部份內容之間的分隔符;Content-Length是報文的長度。
報文體以下所示:dom
------WebKitFormBoundaryKXd7iAk5VsWqoaAY Content-Disposition: form-data; name="userfile1"; filename="2.jpg" Content-Type: image/jpeg ------WebKitFormBoundaryKXd7iAk5VsWqoaAY--
由於傳輸的數據量是未知的,因此經過boundary處理報文體是相當重要的一步。
在瞭解完附件上傳的報文形式之後,接下來咱們將一步步的來實現圖片上傳的全部功能:
exports.saveimg = function (req, res) { if (req.method.toLowerCase() === 'get') { getHandle(req, res); } else if (req.method.toLowerCase() === 'post') { postHandle(req, res); } }
首先,咱們經過請求的方式來進行分支處理,上傳圖片的http請求方式必須是post,postHandle函數的具體實現過程以下:
function postHandle(req, res) { req.setEncoding('binary'); let body = ''; let filename = ''; req.on('data', function (chunk) { body += chunk; }); req.on('end', function () { const boundary = req.headers['content-type'].split(';')[1].replace('boundary=', ''); (1) const file = querystring.parse(body, '\r\n', ':'); (2) if (file['Content-Type'].indexOf('image') !== -1) { const fileAr = file['Content-Disposition'].split('; ')[2].replace('filename=', '').split('.');(3) let filename = fileAr[0];(4) const imageState = fileAr[1].substring(0, fileAr[1].length-1);(5) const entireData = body.toString(); const contentType = file['Content-Type'].substring(1); const upperBound = entireData.indexOf(contentType) + contentType.length;(6) const tarStr = entireData.substring(upperBound).trim(); const boundaryIndex = tarStr.length - boundary.length - 4; const binaryData = tarStr.substring(0, boundaryIndex); //從新設置文件名稱 filename = randomImgString(filename); fs.writeFile(path.join(__dirname, `../../img/${filename}.${imageState}`), binaryData, { encoding: 'binary' }, (err) => { if (err) { utile.errorHandle(err, 'failed write file'); } else { res.writeHead(200, { 'Content-Type': 'application/json' }); const data = JSON.stringify({ 'url':`http://127.0.0.1:3000/${filename}.${imageState}` }) console.log(data); res.write(data); res.end(); } }); } }) }
如下截圖是body的開頭部分:
如下截圖則是body的結束部分:
這段代碼的目的是爲了獲取到報文體中的Content-Disposition字段和Content-Type字段,從Content-Disposition字段中能夠獲取到文件名稱和文件格式,代碼3,4,5則完成了這個功能。
從打印的返回的報文體來看,Content-Type之後的全部數據就是圖片的編碼,因此接下來的任務就是將這個編碼提取出來
隨機生成文件名稱的函數randomImgString的實現過程以下所示:
/** * option to generate randomString */ function randomImgString(filename){ let outString = new Date().toTimeString(); outString += filename.substring(0, filename.indexOf('.')); outString = hash.update(outString) .digest('hex').substring(0, 15); return outString; }
*fs.writeFile(file, data[, options], callback):一、file:文件的存儲路徑 二、data:文件編碼 三、options編碼方式 四、callback:寫入文件成功後的回調函數