Node.js:表單處理與檔案上傳(Form Handling and File Uploads)

表單處理與檔案上傳。

將用戶端資料傳送給伺服器

將使用者的資訊傳遞給伺服器有兩種方法:「查詢字串」(querystring)與「請求內文」(request body)。

看起來似乎使用「查詢字串」的 GET 方式傳送表單資料是不安全的,而使用「請求內文」POST 方式傳送表單資料是安全的。但實際上,兩者只要使用 HTTPS 都是安全的,兩者不使用 HTTPS 都不是安全的。因為如果不使用 HTTPS,我們依然可以看到 POST 的內文資料,容易度與得到 GET 查詢字串無異。

總而言之,基於資料傳輸量的限制和保持瀏覽器網址列的簡潔乾淨,建議使用「請求內文」POST 方式傳送表單資料。

補充說明

範例表單的 HTML 如下。

<form action="/process" method="POST">
  <ul>
    <li>
      <input type="hidden" name="hush" val="hidden, but not secret!" />
    </li>
    <li>
      <label for="fieldColor">
        Your favorite color:
        <input type="text" id="fieldColor" name="color" />
      </label>
    </li>
  </ul>
  <button type="submit">Submit</button>
</form>

編碼

當表單被提交的時候,它必定會以某種方式編碼。

補充說明

表單的處理方式

補充說明

使用 Express 處理表單

Server 端以「name」這個屬性來辨識 HTML Form 的欄位。例如,如下範例,<input type="text" name="name"> 就是以 req.query.name 傳遞給 Server 端。

安裝與設定 body-parser

如果使用 POST 來傳送表單資料,必須要有 middleware 來解析以 URL 編碼的內文。因此要安裝 body-parser。

npm install --save body-parser

然後在 app.js 把它 include 進來:

app.use(require('body-parser')());

我們準備一個表單供使用者填寫後提交。這裡有一個隱藏的欄位「_csrf」,它並不會讓使用者直接在畫面上看見,但傳送表單的時候會將此欄位傳送出去。

備註:由於部落格會把花括號吃掉,因此在左右加一個點,例如「.{.{ }.}.」。

<form class="formNewsLetter" action="/process?form=newsletter" method="POST">
  <div class="formContainer">
    <h2>Sign up for our newsletter to receive news and specials!</h2>
    <input type="hidden" name="_csrf" value=".{.{ csrf }.}." />
    <ul>
      <li>
        <label> Name: <input type="text" name="name" /> </label>
      </li>
      <li>
        <label> Email: <input type="text" name="email" /> </label>
      </li>
    </ul>
    <input type="submit" value="submit" />
  </div>
</form>

在 app.js 中做 router 設定,當表單傳送到 Server 端後,印出欄位值,最後會導到「thankyou」這個成功頁面。

app.post('/process', function(req, res) {
  console.log('Form (from querystring): ' + req.query.form);
  console.log('CSRF token (from hidden form field): ' + req.body._csrf);
  console.log('Name (from visible form field): ' + req.body.name);
  console.log('Email (from visible form field): ' + req.body.email);
  res.redirect(303, '/thankyou');
});

一開始在畫面上會先代入 _csrf 這個欄位的值。

// get index page
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Index', csrf: '0000-1111-2222-3333' });
});

這當中我們沒有使用 client 端的 JavaScript 任何的協助,純粹只是用表單原生的 POST 功能。

Demo

填寫表單,填寫完成後送出。

Node - 使用Express處理表單

Server 端收到 Client 端 POST 過來的資料,並轉到「thankyou」這個成功頁面。

Node - 使用Express處理表單

補充說明

處理 Ajax 表單

在上一個例子中,使用 表單原生的 POST 功能,而接下來要改用 ajax 來處理表單。

我們一樣準備一個表單供使用者填寫後提交。

備註:由於部落格會把花括號吃掉,因此在左右加一個點,例如「.{.{ }.}.」。

<form class="formNewsLetter" action="/process?form=newsletter" method="POST">
  <div class="formContainer">
    <h2>Sign up for our newsletter to receive news and specials!</h2>
    <input type="hidden" name="_csrf" value=".{.{ csrf }.}." />
    <ul>
      <li>
        <label> Name: <input type="text" name="name" /> </label>
      </li>
      <li>
        <label> Email: <input type="text" name="email" /> </label>
      </li>
    </ul>
    <input type="submit" value="submit" />
  </div>
</form>

在 script.js 中,使用 ajax 提交表單後,如果成功的話,就顯示成功訊息,如果失敗就印出錯誤訊息。

var dFormNewsLetter = $('.formNewsLetter');

dFormNewsLetter.on('submit', function(e) {
  e.preventDefault();

  var dThisForm = $(this),
    action = dThisForm.attr('action'),
    $container = dThisForm.find('.formContainer');

  $.ajax({
    url: action,
    type: 'post',
    data: {},
    dataType: 'json',
    error: function(xhr) {
      alert('error: ' + xhr);
    },
    success: function(response) {
      if (response.success) {
        $container.html('<h2>Thank You!</h2>');
      } else {
        $container.html('There is a problem');
      }
    },
  });
});

在 app.js 中,這裡有一個判斷式 req.xhr || req.accepts('json, html') === 'json' ,意即,如果是使用 ajax 請求的話,req.xhrreq.accepts('json, html') === 'json' 皆為 true,那麼就發送成功訊息 { success: true} ,畫面會直接 append 一段程式碼 <h2>Thank You!</h2> 表示表單傳送成功;如果為 false,表示為 form post,則轉跳到「thankyou」頁面。

app.post('/process', function(req, res) {
  if (req.xhr || req.accepts('json, html') === 'json') {
    res.send({ success: true });
  } else {
    res.redirect(303, 'thankyou');
  }
});

這段主要是說明 req.accepts() 會去判斷哪一種回應的類型是最好的,而我們這邊是設定最佳回傳格式是 JSON 或 HTML。當 Server 端收到 Accept HTTP Header 後就會做出判斷。所以,如果是 ajax 請求,或 client side 要求 JSON 或 HTML 或更好的回應,則回應 JSON 物件 { success: true} ,否則就轉跳到「thankyou」頁面。

Demo

填寫表單,填寫完成後送出。

Node - 處理 ajax 表單

送出成功,回應「Thank You!」訊息。

Node - 處理 ajax 表單

Node - 處理 ajax 表單

檔案上傳

檔案上傳有以下兩種方法

安裝 Formidable

npm install --save formidable

設定與使用 Formidable

我們一樣準備一個表單供使用者填寫後提交。因為檔案上傳為 binary data (在此為接受所有圖檔),所以資料編碼使用 multipart/form-data 格式。

備註:由於部落格會把花括號吃掉,因此在左右加一個點,例如「.{.{ }.}.」。

<form
  class="formUpload"
  enctype="multipart/form-data"
  method="POST"
  action="/contest/vacation-photo/{year}/{month}"
>
  <div class="formContainer">
    <h2>Upload a vacation photo for contest!</h2>
    <input type="hidden" name="_csrf" value=".{.{ csrf }.}." />
    <ul>
      <li>
        <label>Name: <input type="text" name="name"/></label>
      </li>
      <li>
        <label>File: <input type="file" name="photo" accept="image/*"/></label>
      </li>
    </ul>
    <input type="submit" value="submit" />
  </div>
</form>

在 app.js 處理 router 的部份。

var formidable = require('formidable');

app.get('/contest/vacation-photo', function(req, res) {
  var now = new Date();
  res.render('cpntest/vacation-photo', {
    year: now.getFullYear(),
    month: now.getmonth(),
  });
});

取得表單後要做解析,若成功則轉至成功畫面,否則轉跳到錯誤畫面。

app.post('/contest/vacation-photo/:year/:month', function(req, res) {
  var form = new formidable.IncomingForm();
  form.parse(req, function(err, fields, files) {
    if (err) {
      return res.redirect(303, '/error');
    }
    console.log('received fields: ');
    console.log(fields);
    console.log('received files: ');
    console.log(files);
    return res.redirect(303, '/thankyou');
  });
});

Demo

Node - 檔案上傳

Node - 檔案上傳

補充說明

jQuery 檔案上傳

我們可能想用一些比較花俏的介面來做檔案上傳的動作,例如可以拖曳、上傳進度或馬上看到上傳後的縮圖。那麼就可以使用一些 jQuery Plugin 來完成。

UI 部份

安裝 jquery-file-upload-middleware

npm install --save jquery-file-upload-middleware

設定與使用 jquery-file-upload-middleware

準備一個表單供使用者填寫後提交。這段程式碼和之前的沒有什麼差異 :(

<form class="formUpload" enctype="multipart/form-data" method="POST">
  <div class="formContainer">
    <h2>Upload a vacation photo for contest!</h2>
    <label>
      File:
      <input
        type="file"
        id="fieldPhoto"
        name="photo"
        accept="image/*"
        data-url="/upload"
        multiple
      />
    </label>
    <div id="uploads"></div>
  </div>
</form>

在 app.js 中處理 router 的部份。相關文件可以參考-jquery-file-upload-middleware

var jqupload = require('jquery-file-upload-middleware');

app.use('/upload', function(req, res, next) {
  var now = Date.now();
  jqupload.fileHandler({
    uploadDir: function() {
      return __dirname + '/public/uploads/' + now;
    },
    uploadUrl: function() {
      return '/uploads/' + now;
    },
  })(req, res, next);
});

我們會將檔案先傳到 local 端的 public 資料夾裡面,亦即 uploadDir 所回傳的路徑,並且在裡面用 now 這個隨機變數分更多資料夾,然後將圖檔存在裡面。而對外的公開路徑是 uploadUrl 所回傳的路徑,例如:http://localhost:3000/uploads/1439806832819/node-form-handling-and-file-uploads.jpg

在 script.js 中,上傳成功後,將上傳成功的檔名列印在畫面上。

var dfieldPhoto = $('#fieldPhoto');

dfieldPhoto.fileupload({
  dataType: 'json',
  error: function(xhr) {
    console.log(xhr);
  },
  success: function(response) {
    console.log(response);
  },
  done: function(e, data) {
    $.each(data.result.files, function(index, file) {
      $('.formContainer').html($('<div class="upload">File uploaded: ' + file.name + '</div>'));
    });
  },
});

Demo

node - jQuery檔案上傳


這篇文章的原始位置在這裡-Node - 表單處理與檔案上傳(Form Handling and File Uploads)

由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。

node.js express encode decode 編碼 解碼