接下来请深呼吸一大片代码正奔涌而来,该项目托管在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:写入文件成功后的回调函数