【译】发送表单数据

这是原文连接:sending form dataphp

许多状况下,咱们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,可是了解一些细节仍是很重要的,以免损坏您的服务器或者给您的用户带来麻烦。html

发送数据到哪?

客户端/服务器架构

web是基于很是简单的客户端/服务器架构的,能够总结为一下两点:前端

  1. 客户端(一般是浏览器)经过HTTP协议发送请求给服务器(大部分服务器就是Apache,Nginx,IIS,Tomcat)
  2. 服务器一样使用HTTP协议来返回响应

客户端/服务器端架构

在客户端,没有比表单更方便并且用户友好的方式来配置HTTP请求去发送数据到服务器端了。这使得用户能够经过HTTP请求来传递信息。python

在客户端:定义怎样发送数据

表单元素定义了怎样发送数据。其全部属性就是被设计用来配置HTTP请求的。最主要的两个属性是action和method。mysql

关于action属性
该属性定义了数据被发送到哪。其值必须是合法的URL。若是没有提供该属性,那么数据发送到当前页面。jquery

例子git

这个例子中,数据被发送到http://foo.comweb

<form action="http://foo.com">

这个例子中,数据被发送到相同的服务器中,可是与当前页面不一样的URLajax

<form action="/somewhere_else">

若是没有指定该属性,那么数据被发送到当前页面sql

<form>

许多之前的网页中使用下面的代码来指明数据应该被发送到当前页面,应为在HTML5以前,该属性是必须的,如今已经没有必要了这样写了

<form action="#">

注意:能够指定URL使用https协议。若是这样作了,那么即便表单自己处于不安全的网页中(使用http协议),表单数据也会被加密。另外一方面,若是表单自己处于安全网页中,可是指定URL使用不安全的http协议,那么每次当用户提交数据时,浏览器都会显示不安全的警告给用户,由于数据没有被加密。

关于method属性

该属性定义了数据怎样被发送。HTTP协议定义了许多种request;表单数据至少能够经过两种方法来发送:GET和POST。

为了理解两种发送方法的不一样,咱们先回顾一下HTTP工做原理。每次当你想要获取一个网络上的资源,浏览器会发送一个请求。这个请求包含两个部分:header和body。其中header包含了一系列的关于浏览器能力的全局元数据,body则是包含一些发送到服务器端的信息(只用部分发送方法的请求有body部分,有些请求没有body部分)。

关于GET方法
GET方法说明浏览器想要服务器端返回一个资源:“嘿,服务器,给我返回这个资源”。这种请款下,浏览器发送的请求没有body部分。由于若是表单采用这种方法发送数据,数据会被添加到URL后面,因此body是空的。

例子

考虑下面的表单:

<form action="http://foo.com" method="get">
  <input name="say" value="Hi">
  <input name="to" value="Mom">
  <button>Send my greetings</button>
</form>

若是使用GET方法,那么请求应该是这样的:

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

关于POST方法
POST方法有些不一样。浏览器但愿服务器处理这些数据:“嘿,服务器,看看这些数据而后给我一个结果”。发送的数据处在请求的body部分。

例子

考虑下面的表单(和上面的同样)

<form action="http://foo.com" method="get">
  <input name="say" value="Hi">
  <input name="to" value="Mom">
  <button>Send my greetings</button>
</form>

如今使用POST方法,那么请求应该是这样的:

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

say=Hi&to=Mom

Content-Length字段指明body的大小,Content-Type字段指明发送到服务器端的资源类型(其实就是编码方式)。

固然,HTTP协议历来不会显示给用户(不过能够经过浏览器的开发者工具来查看)。惟一显示给用户的就是URL。因此在GET方法中,用户能够从网址栏中看见数据,可是在POST方法中,就看不见了。这一点很是重要:

  1. 若是将要发送用户名和密码(或者其余敏感信息),必定不能使用GET方法,不然就会在地址栏显示出来。
  2. 若是将要发送巨量数据,最好使用POST方法,应为某些浏览器会限制URL的长度,而且许多服务器也会限制能够接收的URL长度。

在服务器端:解析并获取数据

无论采用哪一种请求方法,服务器端会接收到一个字符串,而后解析该字符串,获得一系列的键值对。获取这些数据的方式取决于你选择的开发平台和特定开发框架。不一样的技术一样影响到重复键的处理方法;通常状况下,后接受到的优先级更大,会覆盖掉以前的数据。

例子:原生PHP

PHP提供了一些全局对象去获取这些数据。假设咱们使用POST方法提交数据,下面的例子仅仅是获取这些数据而后展现给用户。固然,怎样处理这些数据取决于你。你能够显示数据,存储数据,发送邮件或者其余方式。

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

  echo  $say, ' ', $to;

上面例子的结果是:

Hi Mom

例子:原生Python

本例子使用Python实现相同的功能--显示网页提供的数据。例子中使用CGI Python package来获取数据。

#!/usr/bin/env python
import html
import cgi
import cgitb; cgitb.enable()     # for troubleshooting

print("Content-Type: text/html") # HTTP header to say HTML is following
print()                          # blank line, end of headers

form = cgi.FieldStorage()
say  = html.escape(form["say"].value);
to   = html.escape(form["to"].value);

print(say, " ", to)

显示结果和上面的例子同样:

Hi Mom

其余语言和框架

还有许多其余的服务端技术能够处理表单,包括Perl,Java,.Net,Ruby等等。选择你最喜欢的就行了。可是也没有必要直接使用这些技术去处理表单,由于会比较繁琐。更一般的作法是选择一种框架来辅助处理表单,好比:

  • Symfony for PHP
  • Django for Python
  • Ruby On Rails for Ruby
  • Grails for Java

虽然使用这些框架来处理表单不必定就是很是容易,可是老是好一些,而且能够节省大量时间。

一种特殊状况:发送文件

发送文件对表单来讲是一种特殊状况。文件是二进制数据--至少被看成二进制数据--可是其余数据都是文本数据。由于HTTP协议是一种文本协议,因此处理二进制数据是特殊需求了。

enctype属性

该属性能够指定Content-Type字段的值。该字段很是重要,由于可让服务器端识别出发送的数据类型(编码类型)。默认值是application/x-www-urlencoded。对人类而言,这意味着:“表单数据已经被编码成URL形式了”。

若是咱们想要发送文件,应该须要作两件事:

  1. 设置method属性为POST,由于文件内容不能放到URL中。
  2. 设置enctype属性为multipart/form-data,这样文件会被分割成不少部分发送到服务器。

举个例子:

<form method="post" enctype="multipart/form-data">
  <input type="file" name="myFile">
  <button>Send the file</button>
</form>

注意:有些浏览器的input元素支持multiple属性,从而支持一个input元素能够发送多个文件。服务器端怎样处理这些文件彻底依赖与服务器端选择的技术。前面也提到过,使用框架来处理这些东西会稍微轻松些。

警告:许多服务器都限制了上传文件的大小和HTTP请求的大小,以防止滥用资源。和服务器管理员肯定这个限制的大小是很是重要的。

安全问题

当你每次发送数据给服务器时,你都应该考虑安全问题。HTML表单是服务器的主要威胁之一。不过问题并非来源于表单自己,而是服务器如何处理表单数据。

常见安全漏洞

如下是一些众所周知的安全问题:

XSS和CSRF

跨站点脚本攻击(XSS)和跨站点请求伪造(CSRF)是常见的攻击类型,这一般发生在服务器接收用户的数据而后再次显示这些数据给用户。

XSS使得攻击者能够注入客户端脚本到网页中。一个XSS漏洞可能会让攻击者能够绕过访问控制,好比同源策略。这些攻击的影响可大可小,能够是比较小的干扰,也能够是重大安全风险。

CSRF和XSS有些类似--由于他们都须要注入客户端脚本到网页中--可是CSRF的目的又不同。CSRF攻击者会提高本身的特权(好比网站管理员),而后就能够作一些原本没有权利的操做,好比发送数据给不信任的用户。

XSS攻击利用用户对网站的信任,CSRF攻击利用网站对用户的信任。

为避免这些攻击,你应该检查用户发送到服务器的全部数据,并且不该该显示用户提供的HTML内容。相反,你应该处理这些数据而不是一字不变的显示这些数据。现在大部分框架都实现了基本的HTML字符过滤,好比过滤掉<script>,<iframe>,<object>。这能够下降风险,但仍是不能彻底避免掉。

SQL注入

SQL注入是一种尝试操做网站数据库的一种攻击。攻击者一般会发送SQL请求并指望服务器去执行它(其实就是存储数据),这也是服务器的主要威胁之一

这种攻击的后果是很是严重的,轻一点就是数据丢失,严重的就是特权提高到能够操做全部服务器资源。这种威胁是很是严峻的,因此你永远不该该直接存储用户提交的数据,而是须要作一些检查以及消毒工做(好比在PHP/MySQL应用中使用mysql_real_escape_string())。

HTTP header注入 以及 email 注入

若是你使用表单数据来创建HTTP header或者email,那么就有可能发生这些攻击。他们不会直接攻击服务器或者影响到用户,但倒是更深层次问题的后门,好比会话劫持或者钓鱼攻击。

这些攻击大部分都是静悄悄的,可是却会将你的服务器变为肉鸡

警记:永远不要相信你的用户

那么怎样处理这些威胁呢?这些内容已经超出本章内容了,可是仍然有一些规则须要牢记在心中。最最重要的规则就是:永远不要相信用户,包括本身;由于一个可信任的用户也可能被劫持。

全部到达服务器的数据都须要检查和消毒,老是这样作,不要存在例外。

  • 转义潜在的危险的字符。根据数据内容的不一样以及部署平台的不一样,须要当心的字符也有所区别。可是全部服务器端语言都有相关的函数来处理这种转义。
  • 限制数据的大小以及必须的类型。
  • 上传文件到沙箱(存储这些文件到一个不一样的服务器,并且只容许经过一个不一样的子域名来访问文件,甚至最好是彻底不一样的域名来访问文件)。

若是你遵照上面三条规则,你应该能够避免掉绝大部分的难题了,可是邀请第三方作一个安全审查仍然是一个好主意。永远不要假设你已经解决了全部问题。

总结

如你所见,发送表单数据是很是容易的,可是只作安全的web应用则是复杂的。要记住做为前端开发者不只仅只是定义数据模型。咱们还要作客户端的数据校验,可是服务器端仍然不能信任这些校验结果,由于服务器可没办法知道客户端的真实状况。

另请参见

下面两个连接是关于安全web应用方面的,能够继续参考学习:

  1. The Open Web Application Security Project (OWASP)
  2. Chris Shiflett's blog about PHP Security

下面是关于http方面的连接

  1. GET/POST之enctype
  2. GET vs. POST
  3. What's the difference between “Request Payload” vs “Form Data” as seen in Chrome dev tools Network tab
  4. How are parameters sent in an HTTP POST request?

我的补充

上面更多的内容是关于web安全方面的,下面补充一点method以及enctype方面的信息。

表单中method属性用来指定发送数据的方法:好比GET/POST/PUT等等
表单中enctype属性用来指定发送的数据的编码方式:好比text/plain,application/json,application/x-www-form-urlencoded,application/octet-stream,multipart/form-data等等

GET/POST/PUT最大的不一样固然是语义不同,不过这里只研究对于发送数据的影响。
GET发送的数据位于URL中的query string部分,而URL又处于请求header部分。
POST/PUT发送的数据位置却是同样,都是位于请求body部分。

不一样的method会影响到发送数据的位置,而enctype则会影响到数据的编码方式。
form元素的enctype默认值则是application/x-www-form-urlencoded,因此发送的数据可能长得像这样:

username=name123&password=pass456&age=12&sex=1

注意这种编码形式和GET/POST/PUT是没有关系的。只不过若是是GET方法,那么URL加上问号(?)再加上数据拼接起来。若是是POST/PUT方法,那么数据就存放在请求body部分。

对于jquery中的ajax方法的contentType默认值也是application/x-www-form-urlencoded。
而在backbone中,Backbone.sync方法中有一段代码会作出判断,可能会将contentType设置为application/json。

先说说multipart/form-data,通常在上传文件的时候,须要将表单的enctype设置为multipart/form-data。正如上面译文中所讲,文件属于二进制数据,application/x-www-form-urlencoded是不适用的。
不过这种编码方式过于复杂,不像上面的urlencoded编码,只须要两次分割字符串就能获得全部的键值对。对于这种复杂的编码方式,咱们本身去解析数据,是很难的,幸亏已经有开源的库完成了这一部分功能。
对于单文件上传也许还好,本身了解编码方式之后,解析起来也不是很难。难点在于多文件上传,甚至还有普通表单控件混在其中。

再说说application/json,据我理解urlencoded编码方式只适用于扁平化的键值对,对于嵌套过深的json对象就很难编码了。而application/json就很是适用于嵌套过深的json数据。
在chrome的开发者工具中的network页签中,显示发送数据位于Request Payload。而若是是application/x-www-form-urlencoded,就会显示发送数据位于Form Data。

其实是能够自定义contentType的,也就是自定义编解码的规则。不过只针对ajax有效,对于form元素是不起做用的。由于form元素的编码规则有浏览器控制。而对于ajax,咱们彻底能够本身编码,而后在服务器端本身解码。

最后一点,若是服务器端不能理解Content-Type指定的编码方式,那么应该返回415错误。

相关文章
相关标签/搜索