发送表单数据

一旦表单数据在客户端完成验证,就可以提交表单了。并且,由于我们在上一篇文章中介绍了验证,所以我们现在准备提交!本文将探讨用户提交表单时会发生什么——数据去哪里了,以及当数据到达目的地后我们如何处理它?我们还将探讨与发送表单数据相关的一些安全问题。

先决条件 了解 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 应用程序的信息,您可以深入研究以下资源

高级主题