Go Web 編程 第一章 Web相關概念
第一章 Go與Web應用
Go學習群:415660935
1.1 Web應用
在計算機的世界裏,應用(application)是一個與用戶進行交互,並完成用戶特定任務的軟件程序。而Web應用則是部署在Web之上,並通過Web來使用的軟件程序。一程序滿足以下兩個條件,我們可以把它看做是一個Web應用:
1.這個程序必須向發送命令請求的客戶端返回HTML,而客戶端則會向用戶展示渲染後的HTML。
2.這個程序在向客戶端傳輸數據時必須使用HTTP協議。
在這個定義的基礎上,如果一個程序不是向用戶渲染並展示HTML,而是向其他程序返回某種非HTML格式的數據(例如XML和JSON),那麽這個程序就是一個為其他程序提供服務的Web服務。
1.2 HTTP簡介
協議,是網絡協議的簡稱,網絡協議是指通信計算機雙方必須共同遵從的一組約定。HTTP(Hypertext transfer protocol)即超文本傳輸協議,定義了Web瀏覽器和Web服務器之間進行通信的規則。Web程序中的所有數據都是通過這個看似簡單功能卻異常強大的文本協議傳輸的。這個協議自 20 世紀 90 年代定義以來,至今只進行了3次叠代修改,其中 HTTP 1.1 是目前使用為廣泛的一個版本,而新的一個版本 則是 HTTP 2.0,又稱 HTTP/2。
HTTP 是一種無狀態、由文本構成的請求-響應(request-response)協議,這種協議使用的是客戶端-服務器(client-server)模型。
請求-響應是兩臺計算機進行通信的基本方式,其中一臺計算機會向另一臺計算機發送請求, 而接收到請求的計算機則會對請求進行響應。在客戶端-服務器計算模型中,發送請求的一方(客戶端)負責向返回響應的一方(服務器)發起會話,而服務器則負責為客戶端提供服務。在 HTTP 協議中,客戶端也被稱作用戶代理(user-agent),而服務器則通常會被稱為 Web 服務器。在大多數情況下,HTTP客戶端都是一個Web瀏覽器。
HTTP 是一種無狀態協議,它唯一知道的就是客戶端會向服務器發送請求,而服務器則會向客戶端返回響應,並且後續發生的請求對之前發生過的請求一無所知。
和很多互聯網協議一樣,HTTP也是以純文本方式而不是二進制方式發送和接收協議數據的。 這樣做是為了讓開發者可以在無需使用專門的協議分析工具的情況下,弄清楚通信中正在發生的 事情,從而更容易進行故障排查。
1.3 HTTP請求
HTTP是一種請求-響應協議,協議涉及的所有事情都以一個請求開始。HTTP請求由一系列文本行組成,這些文本行會按照以下順序進行排列:
(1) 請求行(request-line);
(2) 零個或任意多個請求首部(header);
(3) 一個空行;
(4) 可選的報文主體(body)。
一個典型的 HTTP 請求看上去是這個樣子的:
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1 Host: www.w3.org User-Agent: Mozilla/5.0 (empty line) |
這個請求中的第一個文本行就是請求行:
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1 |
請求行中的第一個單詞為請求方法(request method),之後跟著的是統一資源標識符(Uniform Resource Identifier,URI)以及所用的 HTTP 版本。位於請求行之後的兩個文本行為請求的首部。 註意,這個報文的後一行為空行,即使報文的主體部分為空,這個空行也必須存在,至於報文是否包含主體則需要根據請求使用的方法而定。
1.3.1 請求方法
請求方法是請求行中的第一個單詞,它指明了客戶端想要對資源執行的操作。HTTP 0.9 只有 GET一個方法,HTTP 1.0 添加了POST方法和HEAD方法,而HTTP 1.1 則添加了PUT、DELETE、 OPTIONS、TRACE和CONNECT這 5 個方法,並允許開發者自行添加更多方法——很多人立即就把這個功能付諸實踐了。
HTTP 1.1 要求必須實現的只有GET方法和HEAD方法,而其他方法的實現則是可選的,甚至連POST方法也是可選的。 各個 HTTP 方法的作用說明如下:
GET——命令服務器返回指定的資源。
HEAD——與 GET 方法的作用類似,唯一的不同在於這個方法不要求服務器返回報文的主體。這個方法通常用於在不獲取報文主體的情況下,取得響應的首部。
POST——命令服務器將報文主體中的數據傳遞給 URI 指定的資源,至於服務器具體會對這些數據執行什麽動作則取決於服務器本身。
PUT——命令服務器將報文主體中的數據設置為 URI 指定的資源。如果 URI 指定的位置上已經有數據存在,那麽使用報文主體中的數據去代替已有的數據。如果資源尚未存在, 那麽在 URI 指定的位置上新創建一個資源。
DELETE——命令服務器刪除 URI 指定的資源。
TRACE——命令服務器返回請求本身。通過這個方法,客戶端可以知道介於它和服務器之間的其他服務器是如何處理請求的。
OPTIONS——命令服務器返回它支持的 HTTP 方法列表。
CONNECT——命令服務器與客戶端建立一個網絡連接。這個方法通常用於設置 SSL 隧道 以開啟 HTTPS 功能。
PATCH——命令服務器使用報文主體中的數據對 URI 指定的資源進行修改。
1.3.2 安全的請求方法
如果一個 HTTP方法只要求服務器提供信息而不會對服務器的狀態做任何修改,那麽這個方 法就是安全的。GET、HEAD、OPTIONS 和 TRACE 都不會對服務器的狀態進行修改,所以它們都是安全的方法。與此相反,POST、PUT和DELETE都能夠對服務器的狀態進行修改(比如說,在處理 POST 請求時,服務器存儲的數據就可能會發生變化),因此這些方法都不是安全的方法。
1.3.3 冪等的請求方法
如果一個 HTTP 方法在使用相同的數據進行第二次調用的時候,不會對服務器的狀態造成任 何改變,那麽這個方法就是冪等的(idempotent)。根據安全的方法的定義,因為所有安全的方法 都不會修改服務器狀態,所以它們天生就是冪等的。
PUT和DELETE雖然不安全,但卻是冪等的,這是因為它們在進行第二次調用時都不會改變 服務器的狀態:因為服務器在執行第一個PUT請求之後,URI指定的資源已經被更新或者創建出來了,所以針對同一個資源的第二次PUT請求只會執行服務器已經執行過的動作;與此類似,雖然服務器對於同一個資源的第二次DELETE請求可能會返回一個錯誤,但這個請求並不會改變服務器的狀態。
相反,因為重復的POST請求是否會改變服務器狀態是由服務器自身決定的,所以POST方法既不安全也非冪等。
1.3.4 瀏覽器對請求方法的支持
GET方法是基本的 HTTP方法,它負責從服務器上獲取內容,所有瀏覽器都支持這個方法。POST方法從HTML 2.0開始可以通過添加HTML表單來實現:HTML的form標簽有一個名為method的屬性,用戶可以通過將這個屬性的值設置為get或者post來指定要使用哪 種方法。HTML 不支持除GET和POST之外的其他 HTTP 方法:在 HTML5 規範的早期草案中,HTML表單的method屬性曾經添加過對PUT方法和DELETE方法的支持,但這些支持在之後又被刪除了。
話雖如此,但流行的瀏覽器通常都不會只支持 HTML一種數據格式——用戶可以使用 XMLHttpRequest(XHR)來獲得對PUT方法和DELTE方法的支持。XHR是一系列瀏覽器API,這些API通常由JavaScript包裹(實際上XHR就是一個名為 XMLHttpRequest的瀏覽器對象)。 XHR允許程序員向服務器發送HTTP請求,並且跟“XMLHttpRequest”這個名字所暗示的不一樣,這項技術並不僅僅局限於 XML 格式——包括 JSON 以及純文本在內的任何格式的請求和響 應都可以通過XHR發送。
1.3.5 請求的首部
HTTP請求方法定義了發送請求的客戶端想要執行的動作,而HTTP請求的首部則記錄了與 請求本身以及客戶端有關的信息。請求的首部由任意多個用冒號分隔的純文本鍵值對組成,後以回車(CR)和換行(LF)結尾。大多數 HTTP請求首部都是可選的,宿主(Host)首部字段是 HTTP 1.1 唯一強制要求的首部。根據請求使用的方法不同,如果請求的報文中包含有可選的主體,那麽請求的首部還需要帶 有內容長度(Content-Length)字段或者傳輸編碼(Transfer-Encoding)字段。表 1-1 展示了一些 常見的請求首部。
首部字段 |
作用描述 |
Accept |
客戶端在 HTTP 響應中能夠接收的內容類型。比如說,客戶端可以通過 Accept: text/html這個首部,告知服務器自己希望在響應的主體中收到 HTML 類型的內容 |
Accept-Charset |
客戶端要求服務器使用的字符集編碼。比如說,客戶端可以通過 Accept-Charset: utf-8這個首部,告知服務器自己希望響應的主體使用 UTF-8 字符集 |
Authorization |
這個首部用於向服務器發送基本的身份驗證證書 |
Cookie |
客戶端應該在這個首部中把服務器之前設置的所有 cookie 回傳給服務器。比如說,如 果服務器之前在瀏覽器上設置了 3 個 cookie,那麽 Cookie 首部字段將在一個字符串裏 面包含這 3 個 cookie,並使用分號對這些 cookie 進行分隔。以下是一個 Cookie 首部示 例:Cookie: my_first_cookie=hello; my_second_cookie=world |
Content-Length |
請求主體的字節長度 |
Content-Type |
當請求包含主體的時候,這個首部用於記錄主體內容的類型。在發送 POST 或 PUT 請 求時,內容的類型默認為 x-www-form-urlen-coded,但是在上傳文件時,內容的 類型應該設置為 multipart/form-data(上傳文件這一操作可以通過將 input 標 簽的類型設置為file來實現) 異 |
Host |
服務器的名字以及端口號。如果這個首部沒有記錄服務器的端口號,就表示服務器使用 的是 80 端口 |
Referrer |
發起請求的頁面所在的地址 |
User-Agent |
對發起請求的客戶端進行描述 |
1.4 HTTP響應
HTTP 響應報文是對 HTTP 請求報文的回復。跟 HTTP 請求一樣,HTTP 響應也是由一系列 文本行組成的,其中包括:
1) 一個狀態行;
2) 零個或任意數量的響應首部;
3) 一個空行;
4) 一個可選的報文主體。
HTTP 響應的組織方式跟 HTTP 請求的組織方式是完全相同的。以下是 一個典型的 HTTP 響應的樣子(為了節省篇幅,我們省略了報文主體中的部分內容):
200 OK Date: Sat, 22 Nov 2014 12:58:58 GMT Server: Apache/2 Last-Modified: Thu, 28 Aug 2014 21:01:33 GMT Content-Length: 33115 Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/ TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns=‘http://www.w3.org/1999/ xhtml‘> <head><title>Hypertext Transfer Protocol -- HTTP/1.1</title></ head><body>...</body></html> |
HTTP 響應的第一行為狀態行,這個文本行包含了狀態碼(status code)和相應的原因短語 (reason phrase),原因短語對狀態碼進行了簡單的描述。除此之外,這個例子中的 HTTP 響應還 包含了一個 HTML 格式的報文主體。
1.4.1 響應狀態碼
正如之前所說,HTTP 響應中的狀態碼表明了響應的類型。HTTP 響應狀態碼共有 5 種類型, 它們分別以不同的數字作為前綴,如表 1-2 所示。
狀態碼類型 |
作用描述 |
1XX |
情報狀態碼。服務器通過這些狀態碼來告知客戶端,自己已經接收到了客戶端發送 的請求,並且已經對請求進行了處理 |
2XX |
成功狀態碼。這些狀態碼說明服務器已經接收到了客戶端發送的請求,並且已經成 功地對請求進行了處理。這類狀態碼的標準響應為“200 OK” |
3XX |
重定向狀態碼。這些狀態碼表示服務器已經接收到了客戶端發送的請求,並且已經 成功處理了請求,但為了完成請求指定的動作,客戶端還需要再做一些其他工作。 這類狀態碼大多用於實現 URL 重定向 |
4XX |
客戶端錯誤狀態碼。這類狀態碼說明客戶端發送的請求出現了某些問題。在這一類 型的狀態碼中,最常見的就是“404 Not Found”了,這個狀態碼表示服務器無 法從請求指定的 URL 中找到客戶端想要的資源 |
5XX |
服務器錯誤狀態碼。當服務器因為某些原因而無法正確地處理請求時,服務器就會 使用這類狀態碼來通知客戶端。在這一類狀態碼中,最常見的就是“500 Internal Server Error”狀態碼了 |
1.4.2 響應首部
響應首部跟請求首部一樣,都是由冒號分隔的純文本鍵值對組成,並且同樣以回車(CR) 和換行(LF)結尾。正如請求首部能夠告訴服務器更多與請求相關或者與客戶端訴求相關的信息 一樣,響應首部也能夠向客戶端傳達更多與響應相關或者與服務器(對客戶端的)訴求相關的信 息。表 1-3 展示了一些常見的響應首部。
首部字段 |
作用描述 |
Allow |
告知客戶端,服務器支持哪些請求方法 |
Content-Length |
響應主體的字節長度 |
Content-Type |
如果響應包含可選的主體,那麽這個首部記錄的就是主體內容的類型 |
Date |
以格林尼治標準時間(GMT)格式記錄的當前時間 |
Location |
這個首部僅在重定向時使用,它會告知客戶端接下來應該向哪個 URL 發送請求 |
Server |
返回響應的服務器的域名 |
Set-Cookie |
在客戶端裏面設置一個 cookie。一個響應裏面可以包含多個 Set-Cookie 首部 |
WWW-Authenticate |
服務器通過這個首部來告知客戶端,在Authorization 請求首部中應該提供哪種類型的 身份驗證信息。服務器常常會把這個首部與“401 Unauthorized”狀態行一同發 送。除此之外,這個首部還會向服務器許可的認證授權模式(schema)提供驗證信息 (challenge information)(比如 RFC 2617 描述的基本和摘要訪問認證模式) |
1.5 URI
Tim Berners-Lee 在創建萬維網的同時,也引入了使用位置字符串表示互聯網資源的概念。他在 1994 年發表的 RFC 1630 中對統一資源標識符(Uniform Resource Identifier,URI)進行了定義。在這篇 RFC 中,他描述了一種使用字符串表示資源名字的方法,以及一種使用字符串表示 資源所在位置的方法,其中前一種方法被稱為統一資源名稱(Uniform Resource Name,URN), 而後一種方法則被稱為統一資源定位符(Uniform Resource Location,URL)。URI 是一個涵蓋性 術語,它包含了 URN 和 URL,並且這兩者也擁有相似的語法和格式。因為本書只會對 URL 進 行討論,所以本書中提及的 URI 指代的都是 URL。 URI 的一般格式為:
<方案名稱>:<分層部分>[ ? <查詢參數> ] [ # <片段> ]
URI 中的方案名稱(scheme name)記錄了 URI 正在使用的方案,它定義了 URI 其余部分的結構。因為 URI 是一種非常常用的資源標識方式,所以它擁有大量的方案可供使用,不過本書在大多數情況下只會使用HTTP方案。 URI 的分層部分(hierarchical part)包含了資源的識別信息,這些信息會以分層的方式進行 組織。如果分層部分以雙斜線(//)開頭,那麽說明它包含了可選的用戶信息,這些信息將以@ 符號結尾,後跟分層路徑。不帶用戶信息的分層部分就是一個單純的路徑,每個路徑都由一連串 的分段(segment)組成,各個分段之間使用單斜線(/)分隔。
在 URI 的各個部分當中,只有“方案名稱”和“分層部分”是必需的。以問號(?)為前綴 的查詢參數(query)是可選的,這些參數用於包含無法使用分層方式表示的其他信息。多個查詢參數會被組織成一連串的鍵值對,各個鍵值對之間使用&符號分隔。URI 的另一個可選部分為片段(fragment),片段使用井號(#)作為前綴,它可以對 URI 定義的資源中的次級資源(secondary resource)進行標識。當 URI 包含查詢參數時,URI的片段將被放到查詢參數之後。因為 URI 的片段是由客戶端負責處理的,所以 Web 瀏覽器在將URI發送給服務器之前,一般都會先把 URI 中的片段移除掉。如果程序員想要取得URI片段,那麽可以通過 JavaScript 或者某個 HTTP 客戶端庫,將 URI 片段包含在一個 GET請求裏面。讓我們來看一個使用 HTTP 方案的URI示例:http://sausheong:[email protected]. com/ docs/file?name=sausheong&location=singapore#summary。 這個 URI 使用的是 http 方案,跟在方案名之後的是一個冒號。位於@符號之前的分段 sausheong:password 記錄的是用戶名和密碼,而跟在用戶信息之後的 www.example.com/docs/file 就是分層部分的其余部分。位於分層部分高層的是服務器的域名 www.example.com,之後 跟著的兩個層分別為 doc 和 file,每個分層之間都使用單斜線分隔。跟在分層部分之後的是以問號(?)為前綴的查詢參數,這個部分包含了name=sausheong 和 location=singapore 鍵值對,鍵值對之間使用一個&符號連接。最後,這個URI的末尾還帶有一個以井號(#)為 前綴的片段。 因為每個URL都是一個單獨的字符串,所以URL裏面是不能夠包含空格的。此外,因為問號 (?)和井號(#)等符號在 URL 中具有特殊的含義,所以這些符號是不能夠用於其他用途的。為了避開這些限制,我們需要使用URL 編碼來對這些特殊符號進行轉換(URL編碼又稱百分號編碼)。 RFC 3986 定義了 URL 中的保留字符以及非保留字符,所有保留字符都需要進行 URL 編 碼:URL 編碼會把保留字符轉換成該字符在 ASCII 編碼中對應的字節值(byte value),接著把這個字節值表示為一個兩位長的十六進制數字,後再在這個十六進制數字的前面加上一個百分號(%)。 比如說,空格在 ASCII 編碼中的字節值為 32,也就是十六進制中的 20。因此,經過 URL 編碼處理的空格就成了%20,URL 中的所有空格都會被替換成這個值。比如在接下來展示的這個 URL 裏面,用戶名 sau 和 sheong 之間的空格就被替換成了%20:http://www.example.com/docs/file? name=sau%20sheong&location=singapore。
1.6 HTTP/2簡介
HTTP/2 是 HTTP 協議的新版本,這一版本對性能非常關註。HTTP/2 協議由 SPDY/2 協議 改進而來,後者最初是 Google 公司為了傳輸 Web 內容而開發的一種開放的網絡協議。
與使用純文本方式表示的 HTTP 1.x 不同,HTTP/2 是一種二進制協議:二進制表示不僅能夠 讓 HTTP/2 的語法分析變得更為高效,還能夠讓協議變得更為緊湊和健壯;但與此同時,對那些習慣了使用 HTTP 1.x 的開發者來說,他們將無法再通過 telnet 等應用程序直接發送 HTTP/2 報文來進行調試。
跟 HTTP 1.x 在一個網絡連接裏面每次只能發送單個請求的做法不同,HTTP/2 是完全多路復用的(fully multiplexed),這意味著多個請求和響應可以在同一時間內使用同一個連接。除此之 外,HTTP/2 還會對首部進行壓縮以減少需要傳送的數據量,並允許服務器將響應推送(push)至客戶端,這些措施都能夠有效地提升性能。
因為 HTTP 的應用範圍是如此的廣泛,對語法的任何貿然修改都有可能會對已有的 Web 造成破壞,所以盡管 HTTP/2 對協議的通信性能進行了優化,但它並沒有對 HTTP 協議本身的語法 進行修改:在 HTTP/2 中,HTTP 方法和狀態碼等功能的語法還是跟 HTTP 1.1 時一樣。
在 Go 1.6 版本中,用戶在使用 HTTPS 時將自動使用 HTTP/2,而 Go 1.6 之前的版本則在 golang.org/x/net/http2包裏面實現了HTTP/2協議。
1.7 Web應用程序的組成部分
通過前面的介紹,我們知道了 Web 應用就是一個執行以下任務的程序:
(1)通過 HTTP 協議,以 HTTP 請求報文的形式獲取客戶端輸入;
(2)對 HTTP 請求報文進行處理,並執行必要的操作;
(3)生成 HTML,並以 HTTP 響應報文的形式將其返回給客戶端。
為了完成這些任務,Web 應用被分成了處理器(handler)和模板引擎(template engine)這兩個部分。
1.7.1 處理器
Web 應用中的處理器除了要接收和處理客戶端發來的請求,還需要調用模板引擎,然後由模 板引擎生成 HTML 並把數據填充至將要回傳給客戶端的響應報文當中。
1.7.2 模板引擎
通過 HTTP 響應報文回傳給客戶端的 HTML 是由模板(template)轉換而成的,模板裏面可 能會包含 HTML,但也可能不會,而模板引擎(template engine)則通過模板和數據來生成終 的 HTML。
模板可以分為靜態模板和動態模板兩種,這兩種模板都有各自的設計哲學。
1.靜態模板是一些夾雜著占位符的 HTML,靜態模板引擎通過將靜態模板中的占位符替換 成相應的數據來生成終的 HTML,這種做法和 SSI 技術的概念非常相似。因為靜態模 板通常不包含任何邏輯代碼,又或者只包含少量邏輯代碼,所以這種模板也稱為無邏輯 模板。CTemplate 和 Mustache 都屬於靜態模板引擎。
2.動態模板除了包含 HTML 和占位符之外,還包含一些編程語言結構,如條件語句、叠代 語句和變量。JavaServer Pages(JSP)、Active Server Pages(ASP)和 Embedded Ruby(ERB) 都屬於動態模板引擎。PHP 剛誕生的時候看上去也像是一種動態模板,它是之後才逐漸演變成一門編程語言的。
Go Web 編程 第一章 Web相關概念