傳送表單資料

一旦表單資料在客戶端完成驗證,就可以提交表單了。並且,由於我們在上一篇文章中介紹了驗證,所以我們現在準備提交!本文將探討使用者提交表單時會發生什麼——資料去哪裡了,以及當資料到達目的地後我們如何處理它?我們還將探討與傳送表單資料相關的一些安全問題。

先決條件 瞭解 HTML,以及 HTTP伺服器端程式設計 的基本知識。
目標 瞭解提交表單資料時發生的事情,包括對伺服器如何處理資料有一個基本的瞭解。

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

客戶端/伺服器架構

從最基本的層面來說,Web 使用客戶端/伺服器架構,可以概括如下:客戶端(通常是 Web 瀏覽器)向伺服器(大多數情況下是 Web 伺服器,例如 ApacheNginxIISTomcat 等)傳送請求,使用 HTTP 協議。伺服器使用相同的協議回覆請求。

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)和 AMPPS(Mac、Windows、Linux)。

還要注意,如果您使用的是 MAMP 但未安裝 MAMP Pro(或 MAMP Pro 演示試用期已過期),則可能無法正常工作。要使其再次正常工作,我們發現您可以載入 MAMP 應用程式,然後選擇選單選項MAMP > 首選項 > PHP,並將“標準版本:”設定為“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 應用程式的資訊,您可以深入研究以下資源

高階主題