发送表单数据

在客户端验证表单数据后,即可提交表单。由于我们在上一篇文章中已经介绍了验证,所以我们已经准备好提交了!本文将探讨用户提交表单时会发生什么——数据会去向何处,以及我们如何处理到达那里之后的数据?我们还将探讨与发送表单数据相关的一些安全问题。

预备知识 理解 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 应用程序的信息,可以深入研究这些资源