【譯】發送表單數據

這是原文連接: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錯誤。

相關文章
相關標籤/搜索