1. 程式人生 > >Node.js(day2)

Node.js(day2)

一、使用Node實現基本Apache的功能

在上一篇筆記中,我們提到如果開啟一個檔案需要進行一次url判斷是繁瑣的,我們希望我們的Node具有類似Apache這種web伺服器的一個功能:將檔案放到www這個資料夾下,我們只要輸入對應的檔案地址就能訪問到相應檔案。
之前我們是對每一個檔案進行request.url判斷,再處理相應的Content-Type,這樣比較麻煩,現在我們對url進行統一處理即可:

  • 要模擬www檔案的功能,我們先將我們的專案放到www資料夾下:

  • 統一處理訪問www資料夾下檔案的url
var http = require('http');
var fs = require('fs');

var server = http.createServer();
server.listen(8000);
console.log('server running');

server.on('request',function(request,response){
    var url = request.url;
    var rootDir = 'D:/Users/Administrator/Desktop/Node/www';//www資料夾路徑
    var filePath = '/index.html';//檔案地址,預設開啟index.html
    if(url !== '/'){
        filePath = url;
    }

    //根據url開啟檔案
    fs.readFile(rootDir+filePath,function(err,data){
        if(err){
            return response.end('404,Not Fond!');
        }
        response.end(data);
    });
});

原理是一樣的,只不過進行了統一處理;瀏覽器其實會自動判斷data的型別來處理檔案,所以很多時候不用設定Content-Type。

二、案例:使用Node實現顯示檔案目錄的效果

在瀏覽器中輸入目錄地址我們可以看到這樣的效果:

現在使用Node來實現這個效果,我們需要知道fs模組的一個方法:readdir()即讀取目錄:

var http = require('http');
var fs = require('fs');

var server = http.createServer();
server.listen(8000);
console.log('server running');

server.on('request',function(request,response){
    var url = request.url;
    var rootDir = 'D:/Users/Administrator/Desktop/Node/www';//www資料夾路徑

    //讀取目錄
    fs.readdir(rootDir,function(err,data){
        if(err){
            return response.end('404,Not Fond!');
        }
        console.log(data);
    });
});

可以發現,讀取的資料data是以陣列形式顯示出對於路徑下的檔案(夾)名。
現在我們只需要將這些資料顯示在頁面中即可,怎麼將資料變成網頁效果呢,一般情況下我們可以使用模組程式碼來提高效率,類似這樣的邏輯:

其實就是想資料填到對應的位置,而不用去關心相應的頁面效果,頁面效果交給模板程式碼來處理,我們只需要關注兩件事:獲取資料data;將data填入模板程式碼中並使用。
(這裡的模板程式碼我們自己隨便寫一個好了)

1.編寫模板程式碼


<!DOCTYPE html>
<html>
<head>
  <title>DirShow</title>
</head>
<body>
<h1>XXXURL的索引</h1>
<div style="display: block;">
  <a href="#">
    <span>[上級目錄]</span>
  </a>
</div>
<table>
  <thead>
    <tr>
      <th>名稱</th>
      <th class="detailsColumn">
        大小
      </th>
      <th class="detailsColumn">
        修改日期
      </th>
    </tr>
  </thead>
  <tbody id="tbody">
    <tr>
      <td><a href="#">css/</a></td>
      <td>1k</td>
      <td>2018/11/5 上午10:50:48</td>
    </tr>
  </tbody>
</table>
</body>
</html>

效果如上,我們把框起來的部分使用我們讀取的data替換掉就好了:

2.獲取資料

上面已經獲取過,使用readdir()方法來獲取:

3.將資料填充到模板中並使用

我們發現模板程式碼其實就是字串而已,我們將資料填充進去,其實就是進行簡單的字串替換工作即可:

var http = require('http');
var fs = require('fs');

var server = http.createServer();
server.listen(8000);
console.log('server running');

server.on('request',function(request,response){
    var url = request.url;
    var rootDir = 'D:/Users/Administrator/Desktop/Node/www'+url;//根據請求路徑顯示

    //建立模板程式碼
    var contentTemplate = `
        <tr>
          <td><a href="#">filename</a></td>
          <td>1k</td>
          <td>2018/11/5 上午10:50:48</td>
        </tr>
    `;
    var content = '';
    //獲取資料
    fs.readdir(rootDir,function(err,data){
        if(err){
            return response.end('404,Not Fond!');
        }
        //將資料填充到模板程式碼中---字串替換操作
        data.forEach(function(value,index){
            content += contentTemplate.replace('filename',value);
            //更新和使用模板程式碼
        });
        var templateStr = `
        <h1>${rootDir}的索引</h1>
        <div style="display: block;">
          <a href="#">
            <span>[上級目錄]</span>
          </a>
        </div>
        <table>
          <thead>
          <tr>
              <th>名稱</th>
              <th class="detailsColumn">
                大小
              </th>
              <th class="detailsColumn">
                修改日期
              </th>
            </tr>
          </thead>
          <tbody id="tbody">
            ${content}
          </tbody>
        </table>
        `;
        response.setHeader('Content-Type','text/html;charset=utf-8');
        response.end(templateStr);
        });
});

效果如下:

三、模板引擎

模板引擎是什麼這裡就不過多介紹了,從上面的案例中我們已近手動實現了模板引擎要做的事,處理字串,更準確地說應該是將資料填充到模板程式碼這一字串操作過程我們可以通過模板引擎來完成。
模板引擎有諸多優點,這裡不再贅述,我們在使用中感受。比如art-yemplate模板引擎。官網地址:art-yemplate

1.安裝art-template

既然使用了Node.js我們就儘量使用npm來安裝,新建一個資料夾code來儲存程式碼:

2.在瀏覽器中使用art-template

<!DOCTYPE html>
<html>
<head>
    <title>Demo</title>
    <mata charset=utf-8>
    <script type="text/javascript" src="./code/node_modules/art-template/lib/template-web.js"></script>
</head>
<body>
    <!--顯示位置-->
    <p id="show"></p>

    <!--模板程式碼-->
    <script type="text/html" id='demo'>
        大家好,我叫{{ name }},
        我喜歡{{each hobbies}}{{$value}} {{/each}}
    </script>

    <script type="text/javascript">
        //資料
        var data = {
            name: '張三',
            hobbies: ['看書','打遊戲','寫程式碼']
        };
        //使用template方法將資料填充到模板中
        var html = template('demo',data);
        //將模板顯示到頁面上
        window.document.getElementById('show').innerHTML = html;
    </script>
</body>
</html>

注意幾點:

  • 記得引用template-web.js檔案
  • 相關格式要求和api檢視官網文件即可
  • 模板引擎不關心格式以及內容,只是將{{}}表示式中的值進行資料填充,和我們手動replace處理字串的操作是一樣的。
  • 模板引擎還有其他好處,比如根據語句來處理資料,內部引用等。比我們手動處理字串要方便很多,算是一個介紹程式碼工作的工具,且執行速度很快,渲染接近原生js。(有的模板引擎稍慢)

    3.在Node.js中使用art-template

    我們直接在上面的案例上進行改動即可:
var http = require('http');
var fs = require('fs');
var template = require('art-template');//啟用art-template模板引擎

var server = http.createServer();
server.listen(8000);
console.log('server running');

server.on('request',function(request,response){
    var url = request.url;
    var rootDir = 'D:/Users/Administrator/Desktop/Node/www'+url;//根據請求路徑顯示

    //建立模板程式碼
    var templateStr = `
        <h1>{{dir}}的索引</h1>
        <div style="display: block;">
          <a href="#">
            <span>[上級目錄]</span>
          </a>
        </div>
        <table>
          <thead>
          <tr>
              <th>名稱</th>
              <th class="detailsColumn">
                大小
              </th>
              <th class="detailsColumn">
                修改日期
              </th>
            </tr>
          </thead>
          <tbody id="tbody">
            {{each data}}
            <tr>
                <td><a href="#">{{$value}}</a></td>
                <td>1k</td>
                <td>2018/11/5 上午10:50:48</td>
            </tr>
            {{/each}}
          </tbody>
        </table>
        `;
    //獲取資料
    fs.readdir(rootDir,function(err,data){
        if(err){
            return response.end('404,Not Fond!');
        }
        //使用模板引擎填充資料
        var htmStr = template.render(templateStr,{
            dir:rootDir,
            data:data
        });
        response.setHeader('Content-Type','text/html;charset=utf-8');
        response.end(htmStr);
        });
});

值得注意的點:

  • 在Node.js中使用require()來載入模板引擎(注意,如果Node的安裝目錄的下node_modules(預設安裝地址)沒有安裝模板引擎,需要將執行的js放到和模板引擎儲存的資料夾下,比如我們之前是新建了code資料夾來儲存程式碼,所以現在需要將執行的js也放在code下才能找到模板)
  • 使用模板引擎的render()方法來處理模板程式碼(在瀏覽器在中是template()方法)
  • 在瀏覽器中是使用id作為引數,在Node.js中直接使用模板程式碼作為引數即可。
  • 注意模板程式碼要符合使用的模板引擎的規範。${value}是es6中模板字串的變量表達式,而{{}}是art-template模板引擎的表示式。

四、案例:留言板

需求:使用Node.js實現留言板功能,即在如下頁面輸入評論,首頁顯示評論,相關html和css程式碼如下:

1.準備工作

目錄結構如下:

html程式碼(views資料夾下)
js/css/img(public資料夾下,其實只用到了bootstrap.css,可自行網上下載引用)
模板引擎程式碼(node_modules資料夾下)
index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>留言本</title>
  <link rel="stylesheet" href="../public/css/bootstrap.css">
</head>
<body>
  <div class="header container">
    <div class="page-header">
      <h1>Example page header <small>Subtext for header</small></h1>
      <a class="btn btn-success" href="/post">發表留言</a>
    </div>
  </div>
  <div class="comments container">
    <ul class="list-group">
      {{each comments}}
      <li class="list-group-item">
        <span style="color: green">{{ $value.name }}:</span>{{ $value.message }}
        <span class="pull-right">{{ $value.dateTime }}</span>
      </li>
      {{/each}}
    </ul>
  </div>
</body>
</html>

post.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="../public/css/bootstrap.css">
</head>
<body>
  <div class="header container">
    <div class="page-header">
      <h1><a href="/">首頁</a> <small>發表評論</small></h1>
    </div>
  </div>
  <div class="comments container">
    <form action="/addComment" method="post">
      <div class="form-group">
        <label for="input_name">你的大名</label>
        <input type="text" class="form-control" required minlength="2" maxlength="10" id="input_name" name="name" placeholder="請寫入你的姓名">
      </div>
      <div class="form-group">
        <label for="textarea_message">留言內容</label>
        <textarea class="form-control" name="message" id="textarea_message" cols="30" rows="10" required minlength="1" maxlength="20"></textarea>
      </div>
      <button type="submit" class="btn btn-default">發表</button>
    </form>
  </div>
</body>
</html>

404.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <h1>抱歉!  您訪問的頁面失聯啦...</h1>
</body>
</html>

2.編寫app.js檔案(post提交)

var http = require('http');
var fs = require('fs');
var template = require('art-template');

var comments = [];

http.createServer(function(req,res){
    var url = req.url;
    var rootDir = 'D:/Users/Administrator/Desktop/Node/feadback';
    var filePath = '/views/index.html';
    if(url === '/addComment'){
        //request的data事件
        req.on('data',function(postdata){
            //postdata是二進位制資料,postdata.toString()返回的資料編碼方式任然有問題,所以需要解碼
            var decodedata = decodeURIComponent(postdata.toString());//對錶單資料進行解碼--->name=xxx&massage=xxx
            var data = {},key,value,arr;
            decodedata.split('&').forEach(function(v){
                arr = v.split('=');
                key = arr[0];
                value = arr[1] || '';
                data[key] = value;
            });
            data['dateTime'] = new Date().toLocaleString();
            comments.unshift(data);
        });
        url = '/';
    }
    if(url !== '/'){
        filePath = url;
    }
    if(url === '/post'){
        filePath = '/views/post.html';
    }
    if(url !== '/'){
        fs.readFile(rootDir+filePath,function(err,data){
            if(err){
                fs.readFile(rootDir+'/views/404.html',function(err1,data1){
                    if(err1)
                        return res.end('404,Not Fond.');
                    return res.end(data1);
                })
                return;
            }
            res.end(data);
        });
    }else{
        //index.html需要進行模板渲染
        fs.readFile(rootDir+filePath,function(err,data){
            if(err){
                fs.readFile(rootDir+'/views/404.html',function(err1,data1){
                    if(err1)
                        return res.end('404,Not Fond.');
                    return res.end(data1);
                })
                return;
            }
            var html = template.render(data.toString(),{comments:comments});
            res.end(html);
        });
    }
}).listen(8000);
console.log('server running...');

值得注意的一些細節:

  • 注意fileRead()讀取到的資料是二進位制資料,要當做模板程式碼需要使用toString()進行處理。
  • 我們使用的是post提交,那麼表單資料就不會顯示在url上,我們需要使用request的data事件來獲取post提交獲得的資料。
  • postdata也是二進位制資料,且使用toString()方法後編碼任然可能不是正常編碼,需要使用decodeURIComponent()方法進行解碼。
  • 將表單提交的資料新增到陣列中,使用模板引擎渲染到頁面即可。

3.get提交的一些區別

首先,request.url會變成這樣:

也就是多了後面的引數,那麼if(url === '/addComment')就會失效,而request的data事件也不會觸發。
所以如果是get提交,我們需要使用另外的處理方式。

  • 首先使用url核心模板即:var url = require('url');
  • 使用url核心模板的parse()方法來處理request.url。
//將路徑解析為一個方便操作的物件,
//第二個引數為 true 表示直接將查詢字串轉為一個物件(通過 query 屬性來訪問)
 var parseObj = url.parse(req.url, true);
  • 此時parseObj物件就包含了url的相關資訊,如parseObj.pathname表示當前請求連線(不帶hash引數,本例中即為'/addComment')。而parseObj.query可以獲取hash引數轉化而成的物件,本例即:{name:xxx,messagexxx},且不用再使用decodeURIComponent()進行解碼也不會亂碼。