天天看點

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

本節書摘來自華章社群《php精粹:編寫高效php代碼》一書中的第3章,第3.4節http:超文本傳輸協定,作者:(美)  davey shafik,更多章節内容可以通路雲栖社群“華章社群”公衆号檢視

3.4 http:超文本傳輸協定

http(hypertext transfer protocol)是通過導線來傳送web請求和響應的資料傳輸格式。它包含了很多請求和響應的中繼資料,除了這些請求或響應的實體之外,我們也可以利用它來使用web服務。我們也将看到其他的協定,例如xml-rpc和soap,它們也都是建立在http基礎上的。當我們在本章快結尾建構restful服務時,便可以廣泛使用http的功能了。

當我們開發一個簡單的web應用程式時,可能不會那麼重視http。但是如果你想了解緩存、不同檔案類型的傳遞,特别是我們使用web服務時如何使用其他的資料格式,那麼以http為基礎的web服務會讓你受益匪淺。這些内容可能更偏重于理論性,但本節中我們會提供真實的示例并突出http的特性,當你開發或調試任何使用http的内容時,這些特性都會給你提供幫助,如果你忽略這些内容則風險自負。

3.4.1 http信封

你見過一個原始的http請求和響應嗎?讓我們分别通過請求和響應的示例來看看http格式的元件。首先來看看請求。

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定
《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

讓我們一行行地來看,第一行表明正在使用http1.1版本,這個響應的狀态是302 found。這是一個狀态碼,302表示請求的内容在别處(很快我們會深入講解狀态碼),location檔案頭是這個請求的url,content-type檔案頭告訴我們響應中包含什麼格式的正文,與它配對的content-length檔案頭讓我們知道,在這個響應的正文中可以找到什麼并且如何解釋正文。這裡顯示的還有set-cookie檔案頭,它發送cookie來使用後面的請求,并且顯示了請求發送的date。最後,我們看到了正文内容,在本示例中就是用浏覽器顯示的html。

正如你所見,有大量“隐藏”内容包含在http格式中,我們用它們增加用戶端和伺服器間交流的清晰度,其内容是關于我們正在請求的資訊、要了解哪種格式等。當使用web服務時候,可以使用檔案頭全面提高應用程式的健壯性和可預見性。

接下來我們來看看如何發送和調試http請求,然後了解前面示例中曾提及的檔案頭的更多資訊。

3.4.2 發送http請求

在通常情況下,我們可以用不同的方法達到相同的目的。在本節中,我們來看看如何在指令行中使用curl發送web請求,以及在php中使用curl擴充和pecl_http發送web請求。

curl

上一個例子顯示了一個名為curl的程式的實際輸出,它是用于請求url的簡單指令行工具。要請求一個url,你隻需輸入:

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定
《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

本示例中,我們再次使用相同的url從bit.ly中得到短url。我們用curl_init()初始化curl句柄,然後再調用curl_setopt()。如果不設定curlopt_returntransfer,curl_exec()将直接輸出結果而不是傳回結果!一旦curl句柄被正确設定,我們就會調用curl_exec()立即發送請求。我們将響應的正文儲存在$result中,因為正文是json格式,是以這個腳本對它進行解碼然後再将其輸出。

使用php curl擷取檔案頭

這個示例顯示如何得到響應的正文,通常正文中的内容都是我們需要的。然而,如果你還需要檔案頭的資訊,可以調用curl_info()函數,它會傳回無數額外的資訊。

php pecl_http擴充

這個子產品并未預設包含在php中,但我們可以輕松通過pecl來安裝它(更多内容見附錄a)。該子產品提供了一個更先進和更容易實作的接口來處理web請求。如果你的應用程式需要運作很多普通的php安裝程式,pecl_http并不是上策。但如果你要配置一個受你控制的平台,pecl_http絕對值得推薦。如下是一個使用pecl_http的例子。

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

這個簡單請求的代碼結構看起來類似于curl擴充的代碼結構;然而,若我們給這個請求添加很多更複雜的選項,例如發送和接收資料以及檔案頭資訊,pecl_http擴充會更直覺、更容易使用。pecl_http擴充提供程式和面向對象的兩個接口,是以你可以選擇最适合你或你的應用程式的接口。

php流

php可以在本地處理流。如果你在php.ini檔案中啟用allow_url_fopen選項,你可以這麼做。

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

這是抓取一個基本請求的簡潔方式;然而,這種方式可以擴充,就像curl和pecl_http擴充一樣,我們用這種方式處理檔案頭和其他請求方法。要利用這一點,我們就必須使用$context參數,它接受一個有效的上下文。我們使用create_stream_context()函數建立上下文;這個檔案細緻清晰地顯示如何設定主體内容、檔案頭以及處理流的方法。這種做法可能不太直覺,但它的優勢在于被大多數平台預設為直接使用,是以,當應用程式需要多個平台相容時這是種不錯的選擇。

3.4.3 http狀态碼

在前面的例子中,我們看到一個被curl傳回的檔案頭就是狀态檔案頭,它的值為302 found。每個http響應都包含一個狀态碼,這個代碼使我們得到一些初步印象,即這個請求是否成功,又或者出了什麼問題。這個狀态碼總是3位數字,其中每個百位數表示這個響應不同的正常類型。表3.2給出了一個常見狀态碼的概要。

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

當我們使用api的時候,要養成檢查響應狀态碼的習慣。

api中不正确的狀态碼

雖然本節講述了使用狀态碼的正确理論,但是在現實世界中我們發現api根本無視這些理論,對于任何東西都傳回200 ok,這是絕對正常的。這雖不是個好的做法;然而,當內建第三方api的時候你可能會接受這一點。

當我們閱讀本章内容時,将看到如何釋出我們自己的服務,尤其是restful服務如何包含合适的響應檔案頭和論述,如何為狀态碼選擇一個有意義的值。

3.4.4 http檔案頭

我們有大量可使用的http檔案頭數組,并且根據請求和響應來區分它們。在本節中,我們将看到一些最常用的檔案頭以及它們所攜帶的資訊,而且我們會看到如何從php應用程式中讀取和寫入檔案頭。當我們第一次介紹http的時候已經在請求和響應中看到了檔案頭的示例,但是在php中如何管理它們呢?示例如下。

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

在本章的示例中你将看到這樣或類似的代碼。我們可以通過超全局變量$_server得到請求中的資訊,包括accept檔案頭、host、path、get參數等。我們僅僅使用header()函數就可以任意傳回檔案頭到用戶端。

php中的超全局

在php中你肯定很熟悉$_get和$_post變量,它們都是超全局(superglobal)的,這意味着它們被php變量初始化和填充後,可以在任何範圍内使用。$_server是另一個例子,它包含了關于請求的大量有用資訊。

檔案頭必須首先發送給用戶端;我們不能一開始就發送頁面的正文,然後才意識到還需要發送檔案頭!然而,有時候我們的應用程式邏輯不使用這種方式,當我們意識到需要發送檔案頭時腳本已經發送一半了。比如,我們需要以某一方式通過腳本實作當一個使用者沒有登入時發送一個登入頁面給他。我們可以使用如下語句重定向一個使用者。

然而,如果你傳回任何内容之後調用這個函數,你将看到一個錯誤。在理想狀态下,我們希望确認在發送輸出之前我們發送了所有的檔案頭,但是有時候這并不容易做到。但這未必都不可能,我們可以使用輸出緩沖(output buffering)對内容排隊并且先發送檔案頭。

在你的php腳本中可以使用ob_start()來啟用輸出緩沖,或用php.ini設定output_buffering為預設打開,啟用輸出緩沖會導緻php開始存儲你輸出的腳本而不是立即将它們發送到用戶端。當腳本結束或者你調用了ob_flush()函數,php才會将内容發送到用戶端。

如果你打開了輸出緩沖并開始發送輸出,緊接着你會發送一個檔案頭,當緩沖區被清空的時候,檔案頭會在正文内容之前發送到用戶端。這可以讓我們避免代碼輸出先于檔案頭發送的問題。

我們粗略地論及一些常用的檔案頭,現在讓我們認真地看看在應用程式中可能會用到的一些檔案頭,見表3.3。

這并不是一個很全面的清單,如果你想了解更多詳細内容,在wikipedia上有一個很詳盡的清單。不過表3.3概括了我們經常使用的一些基本檔案頭,特别是在本節内容中也将用到它們。在web服務中有兩個我們經常遇到的檔案頭:accept和content-type。

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

在這裡我們看到了一系列被逗号分隔的值,其中一些還包含分号和一個q值,那麼這些表明什麼呢?實際上,一個沒有q值的格式是首選格式,是以,如果一台伺服器可以提供html或xhtml,那麼它就應該選擇沒有q值的格式。如果沒用這種格式,那就退而求其次選擇其他可接受的格式。q值的預設值為1,是以我們降低要求,下一個最好的選擇要求提供xml。如果伺服器不能提供這幾種格式,那麼/表示伺服器不管有什麼樣的格式都應該發送,而用戶端接收結果後将盡可能地處理它們。

accept檔案頭是請求檔案頭的一個組成部分,伺服器接收它之後,計算出哪種格式應該傳回,接着用content-type檔案頭發回響應。這個content-type檔案頭告訴用戶端請求的正文是哪種格式。我們需要知道這些内容以便于更好地了解它們!除此以外,我們想知道是否解碼json、解析xml或者顯示html。content-type檔案頭非常簡單,是以沒有必要提供什麼選擇。

内容類型和錯誤

作為一個規則,我們應該始終用響應被預期的格式傳回響應。這裡有一個常見的錯誤:我們使用web服務通常應該傳回json的時候,卻傳回了html或一些其他格式。用戶端可能無法解析這樣的結果。是以我們應當始終確定以相同的格式傳回,并正确設定所有響應的content-type檔案頭。

通常來說,這些檔案頭不一定都有良好支援或易于了解。然而,這是在web上管理内容協商的最佳方式,是以我們推薦使用這種方法。

3.4.5 http動詞

當我們編寫web表單時,可以在get方法和post方法之間做一個選擇。如下所示是一個基本的表單。

《PHP精粹:編寫高效PHP代碼》——3.4節HTTP:超文本傳輸協定

我們可以看到送出的表單資料和相應的content-type數組不在url中,而在這個請求的正文中。

使用web服務,我們将看到使用了各種各樣的動詞;當我們使用表單時,通常情況下都會使用get方法和post方法,而且你所知的送出資料方式仍然有效。在restful服務中有一些常用的動詞,我們可以使用get、post、put以及delete來建立、檢索、更新、删除資料。在本章後面有更多關于rest的内容。