资讯专栏INFORMATION COLUMN

Node搭建一个DNS服务器

DevTTL / 3031人阅读

摘要:文件用来路由不同的服务器的有三个功能所以包含有三个模块儿,开头就引入了三个模块儿,通过请求的路径名称我们路由到不同的处理模块儿。链接到一个真实的服务器进行域名解析,且始终使用网络进行查询。

Node搭建DNS服务器的过程

接下来请深呼吸一大片代码正奔涌而来,该项目托管在https://github.com/MaxMin-she... 请各位同仁大神view指导。
1、route文件用来路由不同的action

</>复制代码

  1. const DNSserver = require("./src/controller/DNSserver.js");
  2. const Staticfiels = require("./src/controller/Staticfiels.js");
  3. const SaveImg = require("./src/controller/Saveimg.js")
  4. exports.getRouter = function (req, res) {
  5. console.log(url.parse(req.url));
  6. const pathname = url.parse(req.url).pathname;
  7. switch (pathname) {
  8. case "/dns":
  9. DNSserver.parse(req, res);
  10. break;
  11. case "":
  12. case "/":
  13. case "/index":
  14. Staticfiels.index(req, res);
  15. break;
  16. case "/post/img":
  17. SaveImg.saveimg (req, res);
  18. break;
  19. default:
  20. Staticfiels.loadfiels(req, res, pathname);
  21. }
  22. }
  23. module.exports = exports;

DNS服务器的有三个功能所以包含有三个模块儿,开头就引入了三个模块儿,通过请求的url路径名称我们路由到不同的处理模块儿。这个简易的DNS服务器总共有四个自定义的模块:

utile: 自定义的错误处理模块儿

DNSserver:进行DNS域名解析,获取域名所对应的IP地址

Staticfiles: 根据请求路径加载静态文件

Saveimg: 存储图片,返回一个自定的存储路径

接下来,我们分别来介绍这几个模块儿的功能和作用:

utile模块儿

</>复制代码

  1. /**
  2. * handdle error
  3. * @param err
  4. * @param msg
  5. */
  6. exports.errorHandle = function(err, type){
  7. const time = new Date();
  8. console.log(`------------------------
  9. time: ${time}
  10. err: ${err}
  11. type: ${type}
  12. ------------------------
  13. `);
  14. }
  15. module.exports = exports;

该模块儿的作用是通过传入的err,和提示的msg将错误的结果打印出来

DNSserver模块儿

</>复制代码

  1. /**
  2. * DNS解析
  3. */
  4. const url = require("url");
  5. const querystring = require("querystring");
  6. const dns = require("dns");
  7. const util = require("../utile/utile.js");
  8. /**
  9. * @param req
  10. * @param res
  11. */
  12. exports.parse = function(req, res){
  13. const query_url = url.parse(req.url);
  14. const query = querystring.parse(query_url.query);
  15. dns.resolve4(query["hostname"], function(err, addresses){
  16. if(err){
  17. util.errorHandle(err, "DNS failed");
  18. res.writeHead(400);
  19. res.end();
  20. } else {
  21. res.writeHead(200);
  22. res.end(addresses.toString());
  23. }
  24. });
  25. }
  26. module.exports = exports;

req.url:req.url是一个包含着请求基本信息的字符串,以‘http://user:pass@host.com:8080/path?query=string#hash’为例,主要包含的属性字段有:
1、protocal:‘http’,协议类型
2、slashes:true,表示protocal冒号后面跟着两个ASCII 斜杠字符
3、auth:"user:pass",由username:user和password:pass组成
4、host:"host.com:8080",由hostname(域名或者ip地址)和port(端口号)8080组成
5、path:‘/path?query=string’,路径,是由pathname(路径名称:‘/path’)和search(查询名称:‘?query=string’)组成
6、query:‘query=string’,由搜索对象形成

url.parse():url模块的parse方法是将上面所说的这些属性值序列化成键值对对像。

假设我们的请求是:‘http://localhost:3000/dns?hostname=www.google.com’

querystring.parse(str[, sep[, eq[, options]]]):query的属性的值类似于"hostname=www.google.com"这样的值,querystring(查询字符串)模块儿的作用是用来解析和格式化url查询字符串,其中的parse方法是将这种形式的字符串序列化成{hostname:google.com}这样的键值对集合。
str:需要分割的查询字符串
sep:用于界定查询字符串中键值对的符号
eq:用于界定查询字符串中键与值的符号
options:用来定义解码查询字符串的函数和解析键的最大数量

dns.resolve4(str,function(err, ad){}):dns(域名服务器模块儿),这个模块包含两种函数:1、使用底层操作系统工具进行域名解析,无需进行网络通讯。2、链接到一个真实的DNS服务器进行域名解析,且始终使用网络进行查询。resolve4()属于第二种函数。它的作用是使用DNS协议解析IPV4地址主机名,回调函数中的第一个参数是出现的错误,第二个参数是解析得到的ip地址,注意:这里返回的addresses是一个IPV4地址数组,但是res.end()的数据类型只能是string或者buffer,所以在响应是需要回调toString方法,将数组转化成字符串。

Staticfiles模块儿

</>复制代码

  1. /**
  2. * get static files
  3. */
  4. const fs = require("fs");
  5. const path = require("path");
  6. const util = require("../utile/utile.js");
  7. /**
  8. * read Fiels
  9. * @param req
  10. * @param res
  11. * @param pathname
  12. */
  13. const readStaticFiles = function(req, res, filename){
  14. fs.readFile(filename, function(err, data){
  15. if(err){
  16. util.errorHandle(err, "filed readFile");
  17. res.writeHead(404);
  18. res.end("We Got A Problem: File Not Found");
  19. } else {
  20. res.writeHead(200);
  21. res.end(data);
  22. }
  23. })
  24. }
  25. /**
  26. * exports function of reading files
  27. */
  28. exports.loadfiels = function(req, res, pathname){
  29. const filename = path.join("E:static", pathname);
  30. console.log(filename);
  31. readStaticFiles(req, res, filename);
  32. }
  33. module.exports = exports;
  34. /**
  35. * exports function of getting default page
  36. */
  37. exports.index = function(req, res){
  38. const filename = path.join("static", "html/index.html");
  39. readStaticFiles(req, res, filename);
  40. }

Staticfiles文件中有两个输出,index模块是用来处理没有输入文件名时的默认值,loadfiels模块则可以根据文件名返回静态文件,两个模块儿都使用的同一函数readStaticFiles进行文件的读取操作。

path模块儿:用来处理文件和目录的路径

path.join([...paths]):将给定的所有path片段使用平台特定的链接符链接成规范化路径。在这个项目中,由于所有静态文件都放在该项目的static目录下面。所以,请求路径之前要加一个相对路径"static",不然就会报路径错误的error。

fs.readFile(path[,options],callback):根据路径异步读取文件,回调函数中返回两个参数:第一个:error是读取文件过程中产生的错误,第二个:data是读取文件的二进制数据流,如果在option中未指定编码方式,返回的则是一个原始的buffer。

Saveimg模块儿

在这个模块中我们将实现图片上传下载的功能。
首先在html中完成一个form表单:

</>复制代码

"multipart/form-data"是post的一种数据提交方式,用于附件的上传,表单中还有file类型的控件,用于上传一张图片:

接下来,我们了解一下请求报文头和报文体的格式和内容:

请求报文头 req.headers,如下图所示:


在请求报文头中可以找到这些信息,其中Content-Type中的boundary属性很重要,因为附件的数据量比较大,所以一个附件需要多部分提交才能完成,而boundary就是每一部分内容之间的分隔符;Content-Length是报文的长度。
报文体如下所示:

</>复制代码

  1. ------WebKitFormBoundaryKXd7iAk5VsWqoaAY
  2. Content-Disposition: form-data; name="userfile1"; filename="2.jpg"
  3. Content-Type: image/jpeg
  4. ------WebKitFormBoundaryKXd7iAk5VsWqoaAY--

因为传输的数据量是未知的,所以通过boundary处理报文体是至关重要的一步。
在了解完附件上传的报文形式以后,接下来我们将一步步的来实现图片上传的所有功能:

</>复制代码

  1. exports.saveimg = function (req, res) {
  2. if (req.method.toLowerCase() === "get") {
  3. getHandle(req, res);
  4. } else if (req.method.toLowerCase() === "post") {
  5. postHandle(req, res);
  6. }
  7. }

首先,我们通过请求的方式来进行分支处理,上传图片的http请求方式必须是post,postHandle函数的具体实现过程如下:

</>复制代码

  1. function postHandle(req, res) {
  2. req.setEncoding("binary");
  3. let body = "";
  4. let filename = "";
  5. req.on("data", function (chunk) {
  6. body += chunk;
  7. });
  8. req.on("end", function () {
  9. const boundary = req.headers["content-type"].split(";")[1].replace("boundary=", ""); (1
  10. const file = querystring.parse(body, "
  11. ", ":"); (2)
  12. if (file["Content-Type"].indexOf("image") !== -1) {
  13. const fileAr = file["Content-Disposition"].split("; ")[2].replace("filename=", "").split(".");(3
  14. let filename = fileAr[0];(4
  15. const imageState = fileAr[1].substring(0, fileAr[1].length-1);(5
  16. const entireData = body.toString();
  17. const contentType = file["Content-Type"].substring(1);
  18. const upperBound = entireData.indexOf(contentType) + contentType.length;(6
  19. const tarStr = entireData.substring(upperBound).trim();
  20. const boundaryIndex = tarStr.length - boundary.length - 4;
  21. const binaryData = tarStr.substring(0, boundaryIndex);
  22. //重新设置文件名称
  23. filename = randomImgString(filename);
  24. fs.writeFile(path.join(__dirname, `../../img/${filename}.${imageState}`), binaryData, { encoding: "binary" }, (err) => {
  25. if (err) {
  26. utile.errorHandle(err, "failed write file");
  27. } else {
  28. res.writeHead(200, {
  29. "Content-Type": "application/json"
  30. });
  31. const data = JSON.stringify({
  32. "url":`http://127.0.0.1:3000/${filename}.${imageState}`
  33. })
  34. console.log(data);
  35. res.write(data);
  36. res.end();
  37. }
  38. });
  39. }
  40. })
  41. }

req.on("data",callback)绑定了用来监听数据流的事件,req.on("end",callback)监听数据传输完毕的事件,由此可见对传输来的数据进行的一系列操作都应该放在这个监听事件的回调函数当中

body变量中存储的是本次附件上传中存储的所有数据,如下图所示(我只截取了body变量中的一部分):

以下截图是body的开头部分:

以下截图则是body的结束部分:

(1)段代码的作用是从请求报文头的content-type属性值中截取boundary分割符的内容

(2)段代码的作用是提取出报文体中的键值对,querystring模块儿中的parse方法上文有提及,解析后的具体内容如下图所示:


这段代码的目的是为了获取到报文体中的Content-Disposition字段和Content-Type字段,从Content-Disposition字段中可以获取到文件名称和文件格式,代码3,4,5则完成了这个功能。
从打印的返回的报文体来看,Content-Type以后的所有数据就是图片的编码,所以接下来的任务就是将这个编码提取出来

(6)段代码的作用是找到图片编码字符串开始的index

(7)段代码的作用是找到图片编码字符串的结束index,由body的结尾截图可以看出,结束部分是由‘--boundary--’的形式组成,所以最后减去的除了boundary的长度还有两个‘--’的长度4。

(8)段代码中的binaryData则是图片的完整编码

随机生成文件名称的函数randomImgString的实现过程如下所示:

</>复制代码

  1. /**
  2. * option to generate randomString
  3. */
  4. function randomImgString(filename){
  5. let outString = new Date().toTimeString();
  6. outString += filename.substring(0, filename.indexOf("."));
  7. outString = hash.update(outString)
  8. .digest("hex").substring(0, 15);
  9. return outString;
  10. }

*fs.writeFile(file, data[, options], callback):1、file:文件的存储路径 2、data:文件编码 3、options编码方式 4、callback:写入文件成功后的回调函数

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92898.html

相关文章

  • 搭建Kubernetes集群时DNS无法解析问题的处理过程

    摘要:问题描述在搭建集群过程中,安装了插件后,运行一个容器,发现容器内无法解析集群外域名,一开始可以解析集群内域名,一段时间后也无法解析集群内域名。总结通过对问题的探究,也理解了集群中解析的完整过程,如图。 showImg(https://segmentfault.com/img/remote/1460000015639330); 问题描述 在搭建Kubernetes集群过程中,安装了kub...

    snowLu 评论0 收藏0
  • 搭建Kubernetes集群时DNS无法解析问题的处理过程

    摘要:问题描述在搭建集群过程中,安装了插件后,运行一个容器,发现容器内无法解析集群外域名,一开始可以解析集群内域名,一段时间后也无法解析集群内域名。总结通过对问题的探究,也理解了集群中解析的完整过程,如图。 showImg(https://segmentfault.com/img/remote/1460000015639330); 问题描述 在搭建Kubernetes集群过程中,安装了kub...

    Berwin 评论0 收藏0
  • 手动搭建kubernetes集群

    摘要:手动搭建集群探索系列的第三篇,主要记录手动搭建集群的过程,部署部署用作服务发现。配置的子网范围不能和的一致。 手动搭建kubernetes集群 探索kubernetes系列的第三篇,主要记录手动搭建k8s集群的过程,部署dashboard, 部署DNS用作服务发现。顺便记录一下k8s中的一些资源的概念。 配置环境 这个步骤可以参考《Flannel with Docker》文中的步骤,不...

    warnerwu 评论0 收藏0
  • Kubernetes v1.0特性解析

    摘要:问题是不是定义的一个的容器集群是只部署在同一个主机上杨乐到目前是,同一个里的是部署在同一台主机的。问题这个图里的是安装在哪里的所有的客户端以及会连接这个嘛杨乐可以任意地方,只要能访问到集群,会作为的出口。 kubernetes1.0刚刚发布,开源社区400多位贡献者一年的努力,多达14000多次的代码提交,最终达到了之前预计的milestone, 并意味着这个开源容器编排系统可以正式在...

    HackerShell 评论0 收藏0

发表评论

0条评论

DevTTL

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<