03. Nodejs編寫簡單靜態web服務器

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使用大地老師提供的文件,執行結果是:
C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191207194625169.png
C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191207194209690.png
查看運行可以發現這版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"
  }
}

C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191208091708111.png
C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191208091734490.png通過上邊請求迴應截圖我們可以發現css文件的contenttype是正常的,而jpg圖片卻還是html,這是因爲我們在mimiModal中沒有把所有文件對應的擴展名列出來。這裏需要進一步改造,瀏覽器和服務器之間交互涉及的文件類型太多,因此可以利用大地老師提供的一個完善mime對應文件來匹配所有文件對應的contentType。

mime文件內容部分截圖如下:
C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191208092247705.png

第三版

從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倉庫。
C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191208094544848.png
通過查看運行結果發現jpg等圖片的contentType也能正確匹配。至此一個簡單的靜態web服務器完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章