天天看点

《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的内容。