傳送表單資料

在客戶端驗證表單資料後,即可提交表單。由於我們在上一篇文章中已經介紹了驗證,所以我們已經準備好提交了!本文將探討使用者提交表單時會發生什麼——資料會去向何處,以及我們如何處理到達那裡之後的資料?我們還將探討與傳送表單資料相關的一些安全問題。

預備知識 理解 HTML,以及 HTTP伺服器端程式設計 的基礎知識。
目標 瞭解表單資料提交時會發生什麼,包括對資料如何在伺服器上處理有一個基本的概念。

首先,我們將討論表單提交時資料會發生什麼。

客戶端/伺服器架構

最基本來說,Web 使用客戶端/伺服器架構,可總結如下:客戶端(通常是 Web 瀏覽器)使用 HTTP 協議向伺服器(大多數時候是 Web 伺服器,如 ApacheNginxIISTomcat 等)傳送請求。伺服器使用相同的協議響應請求。

A basic schema of the Web client/server architecture

網頁上的 HTML 表單只不過是一種方便使用者的方式,用於配置 HTTP 請求以向伺服器傳送資料。這使使用者能夠提供要在 HTTP 請求中傳遞的資訊。

注意:要更好地瞭解客戶端-伺服器架構的工作原理,請閱讀我們的伺服器端網站程式設計入門模組。

客戶端:定義如何傳送資料

<form> 元素定義了資料將如何傳送。它的所有屬性都旨在讓您配置當用戶點選提交按鈕時要傳送的請求。兩個最重要的屬性是 actionmethod

action 屬性

action 屬性定義了資料傳送到哪裡。其值必須是一個有效的相對或絕對 URL。如果未提供此屬性,資料將傳送到包含表單的頁面的 URL——當前頁面。

在此示例中,資料傳送到絕對 URL — https://example.com

html
<form action="https://example.com">…</form>

在這裡,我們使用相對 URL — 資料傳送到同一源上的不同 URL

html
<form action="/somewhere_else">…</form>

當不帶任何屬性指定時,如下所示,<form> 資料將傳送到表單所在的同一頁面。

html
<form>…</form>

注意:可以指定使用 HTTPS(安全 HTTP)協議的 URL。當您這樣做時,資料會與請求的其餘部分一起加密,即使表單本身託管在透過 HTTP 訪問的不安全頁面上。另一方面,如果表單託管在安全頁面上,但您使用 action 屬性指定不安全的 HTTP URL,則每當使用者嘗試傳送資料時,所有瀏覽器都會向用戶顯示安全警告,因為資料不會被加密。

非檔案表單控制元件的名稱和值以 name=value 對的形式傳送到伺服器,並用和號連線。action 值應該是伺服器上能夠處理傳入資料的檔案,包括確保伺服器端驗證。然後伺服器響應,通常處理資料並載入由 action 屬性定義的 URL,從而導致新頁面載入(如果 action 指向同一頁面,則重新整理現有頁面)。

資料傳送的方式取決於 method 屬性。

method 屬性

method 屬性定義了資料傳送的方式。HTTP 協議提供了幾種執行請求的方式;HTML 表單資料可以透過多種不同的方法傳輸,最常見的是 GET 方法和 POST 方法。

要理解這兩種方法之間的區別,讓我們回顧一下 HTTP 的工作原理。每次您想訪問 Web 上的資源時,瀏覽器都會向 URL 傳送請求。HTTP 請求由兩部分組成:頭部包含一組關於瀏覽器功能的全域性元資料,以及可以包含伺服器處理特定請求所需資訊的主體

GET 方法

GET 方法是瀏覽器請求伺服器返回給定資源的方法:“嘿,伺服器,我想獲取這個資源。”在這種情況下,瀏覽器傳送一個空的主體。由於主體是空的,如果使用此方法傳送表單,則傳送到伺服器的資料將附加到 URL。

考慮以下表格

html
<form action="http://www.foo.com" method="GET">
  <div>
    <label for="say">What greeting do you want to say?</label>
    <input name="say" id="say" value="Hi" />
  </div>
  <div>
    <label for="to">Who do you want to say it to?</label>
    <input name="to" id="to" value="Mom" />
  </div>
  <div>
    <button>Send my greetings</button>
  </div>
</form>

由於使用了 GET 方法,當您提交表單時,您將在瀏覽器位址列中看到 URL www.foo.com/?say=Hi&to=Mom 出現。

The changed url with query parameters after submitting the form with GET method with a "server not found" browser error page

資料作為一系列名稱/值對附加到 URL。在 URL 網址結束之後,我們包含一個問號(?),後面是名稱/值對,每個名稱/值對都由一個和號(&)分隔。在這種情況下,我們向伺服器傳遞兩段資料

  • say,其值為 Hi
  • to,其值為 Mom

HTTP 請求看起來像這樣

http
GET /?say=Hi&to=Mom HTTP/2.0
Host: foo.com

注意:您可以在 GitHub 上找到此示例 — 請參閱 get-method.html也可以線上檢視)。

注意:如果 action URL 方案無法處理查詢(例如 file:),則資料將不會被附加。

POST 方法

POST 方法略有不同。這是瀏覽器在請求考慮 HTTP 請求正文中提供的資料的響應時與伺服器通訊的方法:“嘿,伺服器,看看這些資料並給我發回一個適當的結果。”如果使用此方法傳送表單,資料將附加到 HTTP 請求的正文。

讓我們看一個例子——這與我們在上面的 GET 部分看到的表單相同,但是 method 屬性設定為 POST

html
<form action="http://www.foo.com" method="POST">
  <div>
    <label for="say">What greeting do you want to say?</label>
    <input name="say" id="say" value="Hi" />
  </div>
  <div>
    <label for="to">Who do you want to say it to?</label>
    <input name="to" id="to" value="Mom" />
  </div>
  <div>
    <button>Send my greetings</button>
  </div>
</form>

當使用 POST 方法提交表單時,URL 中不會附加任何資料,HTTP 請求看起來像這樣,資料包含在請求正文中:

http
POST / HTTP/2.0
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

say=Hi&to=Mom

Content-Length 標頭表示正文的大小,Content-Type 標頭表示傳送到伺服器的資源型別。我們稍後將討論這些標頭。

注意:您可以在 GitHub 上找到此示例 — 請參閱 post-method.html也可以線上檢視)。

注意:如果 action URL 方案無法處理請求正文(例如 data:),則將使用 GET 方法。

檢視 HTTP 請求

HTTP 請求從不向使用者顯示(如果您想檢視它們,您需要使用諸如 Firefox 網路監視器Chrome 開發者工具)。例如,您的表單資料將在 Chrome 網路選項卡中顯示如下。提交表單後

  1. 開啟開發者工具。
  2. 選擇“網路”
  3. 選擇“所有”
  4. 在“名稱”選項卡中選擇“foo.com”
  5. 選擇“請求”(Firefox)或“負載”(Chrome/Edge)

然後,您可以獲取表單資料,如下圖所示。

HTTP requests and response data in network monitoring tab in browser's developer tools

唯一向使用者顯示的是呼叫的 URL。正如我們上面提到的,對於 GET 請求,使用者將在其 URL 欄中看到資料,但對於 POST 請求,他們將看不到。這可能由於兩個原因而非常重要

  1. 如果您需要傳送密碼(或任何其他敏感資料),切勿使用 GET 方法,否則您可能會在 URL 欄中顯示它,這將非常不安全。
  2. 如果您需要傳送大量資料,首選 POST 方法,因為某些瀏覽器限制 URL 的大小。此外,許多伺服器限制它們接受的 URL 長度。

伺服器端:檢索資料

無論您選擇哪種 HTTP 方法,伺服器都會收到一個字串,該字串將被解析以獲取資料作為鍵/值對列表。訪問此列表的方式取決於您使用的開發平臺以及您可能與它一起使用的任何特定框架。

示例:原始 PHP

PHP 提供了一些全域性物件來訪問資料。假設您使用了 POST 方法,以下示例只是獲取資料並將其顯示給使用者。當然,您如何處理資料取決於您。您可以顯示它,將其儲存在資料庫中,透過電子郵件傳送,或以其他方式處理它。

php
<?php
  // The global $_POST variable allows you to access the data sent with the POST method by name
  // To access the data sent with the GET method, you can use $_GET
  $say = htmlspecialchars($_POST["say"]);
  $to  = htmlspecialchars($_POST["to"]);

  echo  $say, " ", $to;
?>

此示例顯示了一個包含我們傳送的資料的頁面。您可以在我們的示例 php-example.html 檔案中看到它 — 該檔案包含與我們之前看到的相同的示例表單,其 methodPOSTactionphp-example.php。提交後,它將表單資料傳送到 php-example.php,其中包含上面程式碼塊中看到的 PHP 程式碼。當此程式碼執行時,瀏覽器中的輸出是 Hi Mom

Otherwise blank web page with "hi mom", the data received in response after submitting form data to a php file with POST method

注意:當您在本地瀏覽器中載入此示例時,它將無法工作——瀏覽器無法解釋 PHP 程式碼,因此當表單提交時,瀏覽器只會為您提供下載 PHP 檔案。要使其工作,您需要透過某種 PHP 伺服器執行此示例。本地 PHP 測試的好選擇是 MAMP(Mac 和 Windows)和 XAMPP(Mac、Windows、Linux)。

另請注意,如果您正在使用 MAMP 但未安裝 MAMP Pro(或者 MAMP Pro 試用期已過期),您可能會遇到使其無法正常工作的問題。為了使其再次工作,我們發現您可以啟動 MAMP 應用程式,然後選擇選單選項 MAMP > Preferences > PHP,並將“Standard Version:”設定為“7.2.x”(x 將因您安裝的版本而異)。

示例:Python

此示例展示瞭如何使用 Python 執行相同的操作——在網頁上顯示提交的資料。這使用了 Flask 框架來渲染模板、處理表單資料提交等(請參閱 python-example.py)。

python
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def form():
    return render_template('form.html')

@app.route('/hello', methods=['GET', 'POST'])
def hello():
    return render_template('greeting.html', say=request.form['say'], to=request.form['to'])

if __name__ == "__main__":
    app.run()

上面程式碼中引用的兩個模板如下(如果您嘗試自己執行示例,它們需要位於與 python-example.py 檔案相同的目錄中的 templates 子目錄中)

  • form.html:與我們在POST 方法部分看到的表單相同,但 action 設定為 {{ url_for('hello') }}。這是一個 Jinja 模板,它基本上是 HTML,但可以包含對執行 Web 伺服器的 Python 程式碼的呼叫,這些程式碼包含在大括號中。url_for('hello') 基本上是說“當表單提交時重定向到 /hello”。
  • greeting.html:此模板只包含一行,用於渲染在渲染時傳遞給它的兩段資料。這是透過上面看到的 hello() 函式完成的,該函式在導航到 /hello URL 時執行。

注意:同樣,如果您嘗試直接在瀏覽器中載入此程式碼,它將無法工作。Python 的工作方式與 PHP 略有不同——要在本地執行此程式碼,您需要安裝 Python/PIP,然後使用 pip3 install flask 安裝 Flask。此時,您應該能夠使用 python3 python-example.py 執行示例,然後在瀏覽器中導航到 localhost:5042

其他語言和框架

還有許多其他伺服器端技術可用於表單處理,包括 Perl、Java、.Net、Ruby 等。只需選擇您最喜歡的一種。也就是說,值得注意的是,直接使用這些技術非常不常見,因為這可能很棘手。更常見的是使用許多高質量的框架之一,這些框架使處理表單更容易,例如:

值得注意的是,即使使用這些框架,處理表單也並非一定“容易”。但它比嘗試從頭開始編寫所有功能要容易得多,並且會節省您大量時間。

注意:本文超出了教授您任何伺服器端語言或框架的範圍。如果您想學習它們,上面的連結將為您提供一些幫助。

一個特殊情況:傳送檔案

使用 HTML 表單傳送檔案是一個特殊情況。檔案是二進位制資料——或被認為是二進位制資料——而所有其他資料都是文字資料。因為 HTTP 是一個文字協議,所以處理二進位制資料有特殊要求。

enctype 屬性

此屬性允許您指定表單提交時生成的請求中包含的 Content-Type HTTP 標頭的值。此標頭非常重要,因為它告訴伺服器正在傳送什麼型別的資料。預設情況下,其值為 application/x-www-form-urlencoded。用人類語言來說,這意味著:“這是已編碼為 URL 引數的表單資料。”

如果您想傳送檔案,您需要執行三個額外的步驟

  • method 屬性設定為 POST,因為檔案內容不能放在 URL 引數中。
  • enctype 的值設定為 multipart/form-data,因為資料將被分成多個部分,每個檔案一個部分,加上表單正文中包含的文字資料一個部分(如果文字也輸入到表單中)。
  • 包含一個或多個 <input type="file"> 控制元件,以允許您的使用者選擇要上傳的檔案。

例如

html
<form method="post" action="https://www.foo.com" enctype="multipart/form-data">
  <div>
    <label for="file">Choose a file</label>
    <input type="file" id="file" name="myFile" />
  </div>
  <div>
    <button>Send the file</button>
  </div>
</form>

注意:伺服器可以配置檔案的和 HTTP 請求的大小限制,以防止濫用。

安全問題

每次您向伺服器傳送資料時,都需要考慮安全性。HTML 表單是迄今為止最常見的伺服器攻擊向量(攻擊可能發生的地方)。問題絕不是來自 HTML 表單本身——它們來自伺服器如何處理資料。

我們的伺服器端學習主題的網站安全文章詳細討論了幾種常見的攻擊及其潛在防禦措施。您應該去檢視那篇文章,以瞭解可能發生的情況。

保持偏執:永遠不要相信你的使用者

那麼,如何應對這些威脅呢?這是一個遠遠超出本指南範圍的話題,但有幾個規則需要記住。最重要的規則是:永遠不要相信你的使用者,包括你自己;即使是受信任的使用者也可能被劫持。

所有到達您伺服器的資料都必須經過檢查和淨化。永遠。無一例外。

  • 轉義潛在危險字元。您應該謹慎處理的特定字元會根據資料使用的上下文和您使用的伺服器平臺而異,但所有伺服器端語言都有相應的功能。需要注意的字元序列是看起來像可執行程式碼的字元序列(例如 JavaScriptSQL 命令)。
  • 限制傳入資料量,只允許必要的.
  • 沙盒上傳檔案。將它們儲存在不同的伺服器上,並且只通過不同的子域,或者更好的做法是透過完全不同的域來訪問檔案。

如果您遵循這三個規則,您應該能夠避免許多/大多數問題,但始終建議由合格的第三方進行安全審查。不要假設您已經看到了所有可能的問題。

總結

正如我們上面提到的,傳送表單資料很簡單,但保護應用程式可能很棘手。請記住,前端開發人員不應該定義資料的安全模型。可以執行客戶端表單驗證,但伺服器不能信任此驗證,因為它無法真正知道客戶端發生了什麼。

如果您按順序完成了這些教程,那麼您現在知道如何標記和樣式化表單、進行客戶端驗證,並且對提交表單有了一些瞭解。

另見

如果您想了解更多關於保護 Web 應用程式的資訊,可以深入研究這些資源