03. Nodejs编写简单静态web服务器

之前提到过nodejs与javaphp等纯后台语言的区别是nodejs可以自己编写web服务器,web服务器是用运行于服务器中为浏览器提供响应数据(网页、文件)的程序,世界上现在应用最广泛的有Nginx、Apache、Tomcat、IIS.

github仓库: https://github.com/liangfenggithub/nodejsLearn/tree/master/04.static_web

简单web服务器

利用之前学到的http模块、url模块、fs模块搭建一个简单版的web服务器。实现正常响应web浏览器的请求。

第一版

web服务器说白了就是根据请求的url拿到从本机或者其他服务器上拿到指定的文件或者数据返回给浏览器,他们之间走HTTP协议,底层是TCP协议,不过nodejs封装的比较好,我们不用想单片机使用lwip协议栈似的考虑底层,只需要调用node提供的各种api就能实现。

//简单的web服务器,
var fs = require("fs");
var http = require("http");
var url = require("url");
var app = http.createServer(function (req, res) {
  let data = null;
  let url = req.url;//获取请求的url
  console.log("请求的url是,url:", url);
  if (url == "favicon.ico") {
    return;
  }
  if (url == "/") {
    url = "/index.html";
  }
  fs.readFile(`./static/${url}`, (err, file) => {
    if (err) {
      console.log(`文件 ./static/${url}  不存在`)
    } else {
      res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });  //状态码200,字符集utf8,文件类型html
      res.write(file)//发送响应数据 
      res.end();//结束响应
    }
  })
})
app.listen(9999, "127.0.0.1");
console.log("server listen on 127.0.0.1:9999")

这里的静态html使用大地老师提供的文件,执行结果是:

js服务器和java服务器的性能 js写服务端_html


js服务器和java服务器的性能 js写服务端_nodejs_02


查看运行可以发现这版web服务器能够实现文件返回,但是存在两个问题:

  1. url如果携带查询字符串,会导致文件找不到,因为没有去掉查询字符串,
  2. css文件没有生效,因为返回的Content-type:html而不是正确的文件类型。
  3. 当指定文件不存在时应该返回404状态码和对应404页面

因此需要在改进。

第二版

web服务器接收到浏览器的访问请求后应该过滤掉查询字符串,比如浏览器请求http://127.0.0.1:9999/json/all.json?14859210362172948,实际上请求的是all.json文件,这里可以利用url模块的parse方法来过滤请求参数。

浏览器收到服务器的文件响应后,会根据响应的文件类型做出界面渲染,之前css文件没有生效,是因为没有指定css文件为Content-type:text/css,而是自定其为Content-type:text/html所以没有生效,因此要根据浏览器请求文件的后缀名来确定返回个浏览器回复头的文件类型。这里用到的path模块的exname方法,具体如下。

path.exname 获取后缀名
const path = require("path");
let exname = path.extname("hello.html");
console.log("扩展名是:", exname);
//输出:
扩展名是:.html
完善后的webserver
//简单的web服务器,
var fs = require("fs");
var http = require("http");
var url = require("url");
var path = require("path");
var mimeModal = require("./my_module/mimeModal");
var app = http.createServer(function (req, res) {
  //获取浏览器请求文件名
  let requestUrl = req.url;//获取请求的url
  requestUrl = url.parse(requestUrl).pathname;//过滤掉查询字符串
  console.log("请求的url是,url:", requestUrl);
  if (requestUrl == "/favicon.ico") {
    return;
  }
  if (requestUrl == "/") {
    requestUrl = "/index.html";
  }
  //获取浏览器请求文件的扩展名
  let exname = path.extname(requestUrl);
  console.log("扩展名是:", exname);
  let contentType = mimeModal.getMime(exname);

  fs.readFile(`./static/${requestUrl}`, (err, file) => {
    if (err) {
      console.log(`文件 ./static/${requestUrl}  不存在`);
      //返回404
      fs.readFile("./static/404.html", (err, data404) => {
        if (err) {
          console.log("404文件读取出错")
        } else {
          res.writeHead(404, { "Content-Type": "text/html;charset=UTF-8" });
          res.write(data404);
          res.end();
        }
      })

    } else {
      //根据浏览器请求文件扩展名返回对应的content-type

      res.writeHead(200, { "Content-Type": `${contentType};charset=UTF-8` });  //状态码200,字符集utf8,文件类型html
      res.write(file)//发送响应数据 
      res.end();//结束响应
    }
  })
})
app.listen(9999, "127.0.0.1");
console.log("server listen on 127.0.0.1:9999")

mimeModal.js内容

//通过文件扩展名获取对应的contentType
exports.getMime = function (name) {
  switch (name) {
    case ".css":
      return "text/css";
    case ".html":
      return "text/html"
    case ".js":
      return "text/javascript"
    default:
      return "text/html"
  }
}

js服务器和java服务器的性能 js写服务端_nodejs_03


js服务器和java服务器的性能 js写服务端_js服务器和java服务器的性能_04

通过上边请求回应截图我们可以发现css文件的contenttype是正常的,而jpg图片却还是html,这是因为我们在mimiModal中没有把所有文件对应的扩展名列出来。这里需要进一步改造,浏览器和服务器之间交互涉及的文件类型太多,因此可以利用大地老师提供的一个完善mime对应文件来匹配所有文件对应的contentType。mime文件内容部分截图如下:

js服务器和java服务器的性能 js写服务端_nodejs_05

第三版

从mime.json文件匹配文件扩展名对应的contentType

//简单的web服务器,
var fs = require("fs");
var http = require("http");
var url = require("url");
var path = require("path");
var mimeModal = require("./my_module/mimeModalFromFile");
var app = http.createServer(function (req, res) {

  //获取浏览器请求文件名
  let requestUrl = req.url;//获取请求的url
  requestUrl = url.parse(requestUrl).pathname;//过滤掉查询字符串
  console.log("请求的url是,url:", requestUrl);
  if (requestUrl == "/favicon.ico") {
    return;
  }
  if (requestUrl == "/") {
    requestUrl = "/index.html";
  }
  //获取浏览器请求文件的扩展名
  let exname = path.extname(requestUrl);
  console.log("扩展名是:", exname);
  //通过扩展名获取contentType
  let contentType = mimeModal.getMimeFromFile(exname);
  console.log("contentType", contentType);

  fs.readFile(`./static/${requestUrl}`, (err, file) => {
    if (err) {
      console.log(`文件 ./static/${requestUrl}  不存在`);
      //返回404
      fs.readFile("./static/404.html", (err, data404) => {
        if (err) {
          console.log("404文件读取出错")
        } else {
          res.writeHead(404, { "Content-Type": "text/html;charset=UTF-8" });
          res.write(data404);
          res.end();
        }
      })

    } else {
      //根据浏览器请求文件扩展名返回对应的content-type

      res.writeHead(200, { "Content-Type": `${contentType};charset=UTF-8` });  //状态码200,字符集utf8,文件类型html
      res.write(file)//发送响应数据 
      res.end();//结束响应
    }
  })
})
app.listen(9999, "127.0.0.1");
console.log("server listen on 127.0.0.1:9999")

第三版主程序与第二版大致相同,不同的是contentType的获取

//通过文件扩展名获取对应的contentType
const fs = require("fs");
exports.getMimeFromFile = function (exname) {
  // fs.readFile("my_module/mime.json", (err, data) => {
  //   if (err) {
  //     console.log("mime文件不存在,error:", err)
  //   } else {
  //     let mimejsonData = JSON.parse(data.toString());
  //     console.log("文件内容是:", mimejsonData);
  //     return mimejsonData[exname];//因为mime是一个键值对,因此根据扩展名为键返回值
  //     //注意:由于readFile是一个异步函数,本函数执行完毕后,文件并未读取完成,
  //     //      也就是说崔颖的contentType并未返回,因此会导致webserver主程序错误,
  //     //      解决办法是使用readFileAnsy 同步读取函数。
  //   }
  // })

  const filedata = fs.readFileSync("my_module/mime.json");
  let jsonStr = filedata.toString();
  // console.log("文件内容是:", jsonStr);
  let mimejsonData = JSON.parse(jsonStr)
  return mimejsonData[exname];//因为mime是一个键值对,因此根据扩展名为键返回值
}

需要注意的是这里mime文件的读取是用同步的方式,因为异步会出错。mine文件太大,暂不列出,具体可查看github仓库。

js服务器和java服务器的性能 js写服务端_html_06


通过查看运行结果发现jpg等图片的contentType也能正确匹配。至此一个简单的静态web服务器完成。