Node.js開發系列(六)

上一節咱們實現了手動控制路由的示例,這節咱們來作一個完整的示例html

  • 目錄結構
    基於前面的一些鋪墊,這節會作一個完整的示例。目錄的文件結構以下 :
    圖片描述node

- node-express-pug ==>項目目錄

 - package.json ==>依賴文件

 - server.js ==> 入口文件
 
 - start.js ==> 建立服務
 
 - router.js   ==>  路由中轉
 
 - handlers.js  ==> 路由處理
 
 - views/home.html ==>首頁
 
 - files/ ==>存放上傳文件的目錄

 - node_modules/ ==>依賴項文件目錄
  1. package.json
    首先咱們安裝依賴項,本示例中的package.json文件以下:sql

{
  "name": "application-name",
  "version": "0.0.1",
  "dependencies": {
    "formidable": "latest",
    "mime": "~1.3.4"
  }
}

其中,formidable用於處理form表單數據,mime是一個互聯網標準類型,經過設定它就能夠設定文件在瀏覽器的打開方式。稍後咱們會看到如何使用它們。在項目的文件夾下npm install完成安裝依賴項。express

  • server.js
    首先咱們來實現server.js。在server.js中,咱們主要實現服務器的入口功能,調用各個模塊組件,由其餘模塊實現具體的功能。npm

var server = require('./start');
var router = require('./router');
var handlers = require('./handlers');

var handler = {};
handler[['/','GET']] = handlers.home;
handler[['/show','GET']] = handlers.show;
handler[['/upload','POST']] = handlers.upload;

server.start(router.route,handler);

先導入咱們建立的其餘的模塊文件,而後定義一個handlder對象,這裏配置了對應的路由路徑和其對應的處理方法,相似handler[['/','GET']] = handlers.home;,而後又調用了server模塊的start()方法,並傳入兩個參數,分別是router.route方法和handler對象。下面咱們就來實現start.js文件。json

  • start.js
    start.js的主要功能是負責建立一個server服務,而後對請求的路由進行判斷和過濾,交由router模塊進行處理具體的請求路由。完整的代碼以下瀏覽器

//1.引用模塊
var http = require('http');
var url = require('url');
var formidable = require('formidable');
var querystring = require('querystring');

//2.start(route,handler)方法
//方法的參數是由上層傳遞過來,分別是router.route方法和handler對象
//其中route負責處理路由
//handler對象裏是咱們預先定義的可用處理的路由和對應的請求類型
function start(route,handler) {
    console.log("Start Begin");
    
    //3.監聽1337端口,建立服務器
    var port = process.env.port || 1337;
    http.createServer(onRequest).listen(port);

    //4.建立服務器的回調方法
    function onRequest(req, res) {
        console.log("Request Begin");
        //解析請求的路徑名
        var pathname = url.parse(req.url).pathname; 

        var query = url.parse(req.url).query;
        //POST方法處理
        if (req.method === 'POST'){
            //解析form表單POST方式提交數據
            var form = new formidable.IncomingForm();
             //解析路徑的請求參數,包裝成data向下傳遞
             //function (err, fields, files)是解析成功的回調方法
            form.parse(req, function (err, fields, files) {
                if (err){
                    console.error(err.message);
                    return;
                }
                var data = {fields:fields, files:files};
                execute(pathname,handler,req, res, data);
            });
        }
        //GET方法處理請求
        if (req.method === 'GET'){
            var data = {
            //解析路徑的請求參數,包裝成data向下傳遞
                fields: querystring.parse(query)
            };
            execute(pathname,handler,req, res, data);
        }
        console.log("Request End");
    }
    
    //5.執行處理後的請求
    function execute(pathname, handler, req, res, data) {
        //route執行返回值,若是發生錯誤,統一返回400
        var content = route(pathname,handler,req,res,data);
        if (!content){
            res.writeHead('400',{
                "Content-Type":"text/plain"
            });
            console.log(req.url);
            res.write("400 Bad Request");
            res.end();
        }
    }
}

//6.導出模塊
exports.start = start;

這裏注意服務器

var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) { });

form.parse會解析出不少的屬性,傳給回調參數files,下面是一個我上傳文件後打印的請求參數,參考以下:app

{ fields: {},
  files:
   { fn:
      File {
        domain: null,
        _events: {},
        _eventsCount: 0,
        _maxListeners: undefined,
        size: 4510,
        path: 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\upload_5990006963ce4f2
9de485ddadd819355',
        name: 'sql.sql',
        type: 'application/octet-stream',
        hash: null,
        lastModifiedDate: 2017-03-10T07:00:14.577Z,
        _writeStream: [Object] 
      } 
    } 
}
  • router.js
    咱們看到最後的處理交給了var content = route(pathname,handler,req,res,data);,那麼下面咱們就來看router.jsroute()方法的實現,完整的router.js以下:dom

function route(pathname,handler,req,res,data) {
    console.log('Route');
    var method = req.method;
    if (typeof handler[[pathname,method]] === 'function'){
        return handler[[pathname,method]](res, data);
    }else {
        console.log("No Method found for " + pathname);
        return null;
    }
}

exports.route = route;

還記得咱們的handler對象的格式嗎?咱們的參數傳入瞭解析後的pathname,在這裏又解析了請求的方式var method = req.method;typeof handler[[pathname,method]] === 'function',若是請求路由和請求不在咱們的handler對象的中,即沒有指定的方法調用,那麼咱們就直接返回null,若是有,就調用這個方法。打個比方handler[['/show','get']](res, data)等同於handlers.show(res, data)

  • home.html
    在home.html中咱們定義了簡單的form表單,實現一個上傳文件的功能。

其中上傳文件的input標籤的name="fn".

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HOME</title>
</head>
<body>
    <p>This is Home</p>
    <h1>File Manager</h1>
    <hr>

    <a href="/show">Show All Files</a>
    <form action="/upload" method="post" enctype="multipart/form-data" >
        <input type="file" name="fn">  
        <input type="submit" value="Upload File">
    </form>
</body>
</html>
  • handlers.js
    這裏纔是咱們對各個正確路由的響應。如下是完整的代碼,我會逐一解釋說明。

//1.引入模塊
var fs = require('fs');
var path = require('path');
var mime = require('mime');

//2.home方法,即瀏覽器請求`http://127.0.0.1:1337`的方法
//這裏咱們直接讀取views/home.html文件,返回將頁面展示出來
function home(res, data) {
    fs.readFile('views/home.html', function (err, data) {
        res.writeHead(200, {"Content-Type": "text/html"});
        //注意這裏的data並非home的參數,而是讀取文件成功後的回調data
        res.write(data);
        res.end();
    });
    return true;
}

//3.show方法 
//show方法會處理兩種類型的請求,兩種類型的請求都是get類型
//data.fields && data.fields['fn'] 這裏處理的有上傳的文件後,點擊文件的連接下載文件
function show(res, data) {
    //解析參數的data的值,這裏用fn是由於後面form表單中定義的值是fn
    if (data.fields && data.fields['fn']){
        //獲取文件名稱
        var name = data.fields['fn'];
        //取得文件完整名稱
        var file = path.join(__dirname, '/files', name);
        //經過文件名指定mime類型
        var mimeType = mime.lookup(file);
        //設定響應頭
        res.setHeader('Content-disposition','attachment;filename=' + name);
        res.setHeader('Content-Type',mimeType);
        //讀取文件數據
        var fileData = fs.readFileSync(file,'binary');
        //響應給給用戶
        res.end(fileData, 'binary');
        
        //這裏會直接返回,並不會走下面的方法
        return true;
    }

    //若是用戶不是點擊的下載連接進行show方法
    //那麼就讀取文件列表顯示在界面上,而且提供下載功能
    fs.readdir('files', function (err, list) {
        console.log(list);
        res.writeHead(200, {"Content-Type": "text/html"});
        var html = '<html><head></head>' +
            '<body><h1>File Manager</h1>';
            //有文件就生成文件列表
        if (list.length) {
            html += "<ul>";
            for (i = 0; i < list.length; i++) {
                html += '<li><a href="/show?fn=' +
                    list[i] + '">' +
                    list[i] + '</a></li>';
            }
            html += "</ul>";
        } else {
        //沒有文件
            html += '<h2>No files found</h2>';
        }
        html += '</body></html>';
        res.write(html);
        res.end();
    });
    return true;
}

//4.上傳文件的方法,方法是響應home.html中的form表單
function upload(res, data) {
    var temp = data.files['fn'].path;
    var name = data.files['fn'].name;
    //調用複製文件的方法
    copyFile(temp,path.join('./files',name),function (err) {
        if (err){
            console.log(err);
            return false;
        }else {
            return true;
        }
    });
}

//定義複製文件的方法
function copyFile(source, target, callback) {
    //讀文件流
    var rs = fs.createReadStream(source);
    rs.on('error',function (err) {
        callback(err);
    });
    
    //寫文件流
    var ws = fs.createWriteStream(target);
    ws.on('error',function (err) {
        callback(err);
    });
    ws.on('finish',function () {
        callback();
    });
    //寫入,並覆蓋源文件的內容
    rs.pipe(ws);
}

//導出方法
exports.home = home;
exports.show = show;
exports.upload = upload;
  • 小結
    在項目文件夾下node server啓動http服務器,在瀏覽器中輸入http://127.0.0.1:1337

圖片描述

在上傳文件後點擊Show All Files,能夠看到文件列表,點擊其中一個,便可下載。
圖片描述

這樣咱們就完成了一個完整的node處理表單,文件上傳和下載的示例。

相關文章
相關標籤/搜索