HTTP协议详解
HTTP协议是一个应用层的通信规范: 双方要进行通信, 大家都要遵守一个规范——HTTP协议。HTTP协议从WWW服务器传世超文本到浏览器, 可以使浏览器更加高效。
HTTP(Hyper Text Transfer Protocol, 超文本传输协议)
是万维网协会(Word Wide Web Consortium)
和Internet 工作小组(Internet Engineering Task Force, IETF)
合作的结果, 最终发布了一些列的RFC(Request For Comments)
。RFC 1945
定义了HTTP 1.0版本, 最著名的RFC 2616
, 其中定义了目前普遍使用的版本——HTTP1.1。
HTTP是一个应用层协议, 由请求和响应构成, 是一个标准的客户端服务器模型。HTTP通常承载于TCP协议之上, 也有时候承载在TLS或SSL协议层之上, 这个时候就成了常说的HTTPS。
HTTP默认端口为80, HTTPS的端口是443。
HTTP协议是一个无状态的协议, 同一个客户端的这次请求和上次请求没有对应关系。
sequenceDiagram
Client->>Server: 请求
Server->>Client: 响应
这种设计属于问答式交互, 客户端和服务器一问一答, 使HTTP协议模型异常简单。但是这种设计也存在一些问题, 例如服务端不会主动向客户端PUSH, 一问一答的轮询也会使TCP连接频繁建立和断开, 导致其交互效率不高。基于上面缺点, SPDY协议诞生了。
SPDY
协议由谷歌推出, 优化浏览器和服务器之间的通信, 支持流复用, 具备优先级的请求、主动发起请求、强制SSL安全传输等先进特性。
目前Chrome 和 Firefox浏览器的最新版本都支持 SPDY
, 一些服务端软件支持SPDY, 如Jetty 8、Nginx 1.3.x等。
SPDY 协议的应用需要客户端浏览器和服务端同时支持。 目前应用SPDY 协议主要是Google的产品, 如Google Plus。
HTTP 协议如何工作
浏览网页是HTTP协议的主要应用, 但是不代表HTTP协议只能用于网页的浏览。只要通信的双方都遵守HTTP协议, 就有用武之地。
HTTP协议如何工作:
首先, 客户端发送一个请求(Request) 给服务器, 服务器在接收到这个请求后将生成一个响应(Response)返回给客户端。一次HTTP操作称之为一个事务, 其工作过程可分为四步:
- 客户端与服务器建立连接。点击某个超链接, HTTP 协议开始工作
- 建立连接后, 客户机发送一个请求给服务器。格式为: 前面是统一资源标识符(URL)、中间是协议版本号、后面是MIME信息(包括请求修饰符、客户机信息和可能的内容)
- 服务器接到请求后, 给予相应的响应信息。格式为: 首先是一个状态行(包括信息的协议版本号、一个成功或错误的代码), 然后是MIME信息(包括服务器信息、实体信息和可能的内容)
- 客户端接收服务器返回的信息并显示在用户显示屏上, 然后断开与服务器的连接
关于HTTP协议, 可以查看RFC 2616文档。
常用的抓包软件有IRIS、Wireshark等。
下面介绍HTTP协议中的一些主要的概念
- 请求
在发起请求前, 需要先建立连接。
连接是一个传输层的实际环流, 它建立在两个相互通信的应用程序之间。
HTTP 1.1协议中, Request 和 Response 头中都可能出现一个 connection 的头, 其决定 Client和Server通信时对于长链接如何处理。
HTTP 1.1 中, Client和Server默认对方支持长链接, 如果CLient不希望使用长链接, 需要在Header中指明connection 为 cliose; 如果Server 不想支持长链接, 则在response中需要明确说明connection 为close。
- 响应
在接收和解释请求消息后, 服务器返回一个HTTP响应消息。HTTP响应也由三个部分组成: 状态行、消息报头、响应正文。
状态码由三位数字组成, 第一个数字定义了响应的类别, 有五种可能取值:
1xxx: 指示信息——请求已被成功接收, 继续处理
2xx: 成功——请求已被成功接收、理解、接受
3xx: 重定向——要完成请求必须进行更进一步的操作
4xx: 客户端错误——请求有语法错误或请求无法实现
5xx: 服务器端错误——服务器未能实现合法请求
- 报头
HTTP消息报头包括普通报头、请求报头、响应报头、实体报头
- 普通报头中有少数报头域用作所有的请求和响应消息, 但并不用作于被传输的实体, 只用于传输的消息(如缓存控制、连接控制等)
- 请求报头允许客户端向服务端传递请求的附加信息以及客户端自身的信息(如UA头、Accept等)
- 响应报头允许服务器传递不能放在状态行中的附加响应信息, 以及关于服务器的信息和对 Request-URI 所标识的资源进行下一步访问的信息(如Location)。
- 实体报头定义了关于实体正文和请求所标识的资源的元信息, 例如有无实体正文。
比较重要的几个报头如下:
- Host: 头域指定请求资源的Internet主机和端口号, 必须表示请求URL的原始服务器或网关的位置。HTTP 1.1 请求必须包含主机头域, 否则系统会以400状态码返回。
- User-Agent: 简称UA, 内容包含发出请求的用户信息。通常UA包含浏览者的信息, 主要是浏览器的名词版本和所用的操作系统。这个UA不仅仅使用浏览器才存在, 而是所有使用HTTP协议的客户端都会发送, 这个UA头是区分客户端所使用设备的重要依据。
- Accept: 告诉服务器可以接受的文件格式。
- Cookie: Cookie分两种, 一种是客户端向服务器发送的, 使用Cookie报头, 用来标记一些信息, 另一种是服务器发送给浏览器的, 报头为Set-Cookie。二者主要却别在于Cookie报头的value可以有多个Cookie值, 并且不需要显示指定domain等。而Set-Cookie报头里一条记录只能有一个Cookie的value, 需要指明domain、path等
- Cache-Control: 指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache0Control并不会修改另一个消息处理过程中的缓存处理机制。请求时的缓存指令包括
no-cache
、no-store
、max-age
、max-stale
、min-fresh
、only-if-cached
; 响应消息中的指令包括public
、private
、no-cache
、no-store
、no-transform
、must-revalidate
、proxy-revalidate
、max-age
。 - Referer: 头域允许客户端指定请求URI的资源源地址, 这可以允许服务器生成回退链表, 可以用来登录、优化缓存等。也允许废除的或错误的连接由于维护的目的被追踪。
- Content-Length: 内容长度
- Content-Range: 响应的资源范围。可以在每次请求中标记请求的资源范围, 在连接断开重连时, 客户端只请求该资源未下载的部分, 而不是重新请求整个资源, 实现断点续传。
- Accept-Encoding: 指定所能接受的编码方式。通常服务器会对页面进行GZIP压缩后再输出, 以减少浏览, 一般浏览器均支持这种压缩后的数据进行处理。
- 自定义报头: 在HTTP消息中, 也可以使用一些Http1.1中未定义的头字段, 这些字段统称为自定义的HTTP头或者扩展头。
PHP中一系列与HTTP协议相关的一系列函数:
- array get_headers(string $url[, int $format]) 函数: 取得服务器响应一个HTTP请求所发送的所有标头。通常用此函数请求一个URL, 根据其返回的数据判断状态码是否为200, 即可判断所有请求的资源是否存在。
- file 系列函数: 包括fopen、file_get_contents等, 可以用来操作文件, 也可以用来请求一个网络上的资源。
- stream_* 系列函数: 发送请求, 包括但不限于HTTP协议。
- socket 系列函数: 通过Socket发送和请求数据, 包括但不限于HTTP协议。
- cURL 扩展库: PHP的一个扩展, 这是一个封装的函数库。可以用来模拟浏览器和服务器进行交互, 功能比较强大。
- header 函数: PHP中可用此函数发送原始的HTTP头。需要的是, 这个函数之前不能有输出以及空格等。
简单的HTTP协议使用示例
<?php
$html = file_get_contents('http://www.baidu.com/');
print_r($http_response_header);
$fp = fopen('http://www.baidu.com/', 'r');
print_r(stream_get_meta_data($fp));
fclose($fp);
context 参数使这些函数更加灵活, 通过该参数可以定制HTTP请求, 设置POST数据。
<?php
$data = array('author' => 'pchangl', 'mail' => 'pchangl@163.com', 'text' => 'test content.');
$data = http_build_query($data);
$opts = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded\r\n' .
'Content-Length:' . strlen($data) . "\r\n",
'content' => $data
)
);
$context = stream_context_create($opts);
$html = @file_getcontents('http://www.baidu.com/', false, $context);
垃圾信息防御措施
- IP限制: 原理在于IP难以伪造, 即使对于拨号用户, 虽然IP可变, 但也会大大增加攻击的工作量
- 验证码: 重点是让验证码难于识别, 对于数字+字母的验证码, 关键在于形变和重叠, 增加其破解中切割和字模对比的难度, 人眼尚且难以辨识, 机器就更难处理了, 再者是加大对于验证码的猜测难度。
- Token和表单欺骗: 通过加入隐藏的表单或者故意对程序混淆表单值, 进而达到判断是真实用户还是软件提交的目地。
- 审核机制: 加大了管理人员的工作量, 但理论上可以完全阻止垃圾信息, 这时最无奈也是最有效的策略。
Socket进程通信机制
Socket 通常称为套接字, 用于描述IP地址和端口, 是一个通信链的句柄。应用程序通过套接字向网络发出请求或者应答网络请求。Socket既不是一个程序, 也不是一种协议, 只是操作系统提供的通信层的一组抽象API。
进程通信相关概念
为保证两个相互通信的进程之间即互不干扰又协调一致工作, 操作系统为进程通信提供了相应措施, 如UNIX BSD中的管道(pipe)、命名管道(named pipe)和软中断信号(signal), 以及UNIX System V的消息(message)、共享存储区(shared memory)和信号量(semaphore)等, 但这些都仅限于在本机进程之间的通信。
操作系统支持网络协议众多, 不同协议工作方式不同, 地址格式也不同, 因此, 网间通信还要解决多重协议的识别问题。
为了解决上述问题, TCP/IP协议引入了下列概念。
- 端口
网络中可以被命名和寻址的通信端口, 是操作系统可分配的一种资源。
端口是一种软抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用某端口建立连接后, 传输层给该端口的数据都被相应进程所接收, 相应进程发给传输层的数据都通过该端口输出。
TCP和UDP协议完全是两个独立的两个软件模块, 因此各自端口号也相互独立, TCP有一个255号端口, UDP也有一个255端口号, 二者并不冲突。TCP和UDP都有0~65535个端口号
- 端口号小于256的定义为常用端口, 服务器一般通过常用端口号识别
- 客户端只需要保证该端口号在本机是唯一的。客户端口号因为存在时间短暂, 又称临时端口号
- 大多数TCP/IP实现给临时端口号分配1024~5000之间的端口号, 大于5000的端口是给其他服务预留的
常见的端口有FTP的21号端口, HTTP服务的80端口, SMTP的25端口和HTTPS的443端口。
- 地址
网络通信中通信的两个进程分别处在不同的机器上。遵循以下原则:
某台主机可与多个网络相连, 必须指定一个网络地址
网络上每台主机应有其唯一地址
每台主机的每个进程应有在该主机上唯一标识符。
连接
两个进程间的通信链路称为连接, 连接表现为一些缓冲区和一组协议机制。
SOCKET socket(int af, int type, int protocol);
- af: 指定应用程序使用的通信协议的协议族, 对于TCP/IP协议族该参数设置AF_INET, 对于UNIX可建立本地Socket。
- Type: 指定创建的Socket类型
- 流套接字类型(SOCK_STREAM): 最常见的类型, 基于TCP协议
- 数据报套接字类型(SOCK_DGRAM): 即UDP数据报
- 原始套接字类型(SOCK_RAW): 在IP层对套接字进行编程, 实际上就是在IP层构造自己的IP包, 然后把这个包发出去
- protocol: 指定应用程序所使用的通信协议, 通常是TCP协议与UDP协议
php中的socket函数
resource socket_create(int $domain, int $type, int $protocol)
bool socket_bind(resource $socket, string $address[, int $port = 0])
bool socket_listen(resource $socket [, int $backlog = 0])
bool socket_set_block(resource $socket)
int socket_write(source $socket, string $buffer [, int $length = 0])
string socket_read(resource $socket, int $length [, int type = PHP_BINARY_READ])
pfsockopen(string $hostname [, int $port = -1 [, int & $errno [, string & $errstr [, float $timeout = ini_get("default_socket_timeout")]]]])
bool socket_set_option(resource $socket, int $level, int $optname, mixed $optval)
int socket_last_error([resource $socket])
cURL 工具及应用
建立cURL请求的基本步骤:
- 初始化
- 设置选项, 包括URL
- 执行并获取HTML文档内容
- 释放cURL句柄
<?php
// 1. 初始化
$ch = curl_init();
// 2. 设置选项, 包括URL
curl_setopt($ch, CURLOPT_URL, "http://www.php.net");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //将curl_exec() 获取的信息以文件流的形式返回,
// 而不是直接输出
curl_setopt($ch, CURLOPT_HEADER, 1); //启用时会将头文件的信息作为数据流输出
// 3. 执行并获取 HTML 文档内容
$output = curl_exec($ch);
// 4. 释放cURL句柄
curl_close($ch);
echo $output;
cURL 常用选项
选项 | 描述 |
---|---|
CURLOPT_AUTOREFERER | 当根据Location 重定向时, 自动设置 header 中的 Referer 信息 |
CURLOPT_COOKIESESSION | 启用时 cURL 会仅仅传递一个 Session Cookie, 忽略其他 Cookie, 默认情况下 cURL 会将所有 Cookie 返回给服务器。Session Cookie 指用来判断服务端的 Session 是否有效而存在的 Cookie |
CURLOPT_FOLLOWLOCATION | 启用将服务器返回的 Location 放在 Header 中, 递归的返回给服务器, 使用 CURLOPT_MAXREDIRS 可以限定递归返回的树立 |
CURLOPT_HEADER | 启用时将头文件的信息作为数据流输出 |
CURLOPT_RETURNTRANSFER | 将 curl_exec() 获取的信息以文件流的形式返回, 而不是直接输 |
CURLOPT_INFILESIZE | 设定上传文件的大小, 单位为字节(byte) |
CURLOPT_MAXCONNECTS | 运行最大连接数量, 超过会通过CURLOPT_CLOSEPOLICY 决定应该停止哪些连接 |
CURLOPT_MAXREDIRS | 指定 HTTP 重定向的最多数量, 和 CURLOPT_FOLLOWLOCATION 一起使用 |
CURLOPT_COOKIE | 设定 HTTP 请求中 Cookie 部分的内容, 多个 Cookie 用分号分隔, 分号后带一个空格 |
CURLOPT_COOKIEFILE | 包含 Cookie 数据的文件名, Cookie 文件的格式可以是 Netscape 格式, 或者只是纯 HTTP 头部信息存入文件 |
CURLOPT_ENCODING | HTTP 请求头中 Accept-Encoding 的值, 支持的编码有 identity, deflate 和 gzip。如果空字符串"", 请求头会发送所有支持的编码类型 |
CURLOPT_POSTFIELDS | 全部数据使用 HTTP 协议中的 POST 操作来发送。 要发送文件, 在文件名前面加上@前缀并使用完整路径。这个参数通过 urlencoded 后的字符串类似 paral=val1¶2=varl2&... 或使用一个一字段名为键, 字段数据为值的数组。如果value是一个数组, Content-type 头会被设置成 multipart/form-data |
CURLOPT_RANGE | 以 X-Y 的形式组成, 其中 X 和 Y 都是可选项获取数据的范围, 单位是字节。 HTTP 传输线程也支持几个这样的重复项中间用逗号分隔如 X-Y,N-M |
CURLOPT_REFERER | HTTP 请求中 Referer 的内容 |
CURLOPT_HTTPHEADER | 用来设置 HTTP 头字段的数组。数组形式如下: array('Content-type: text/plain', 'Content-length: 100') |
CURLOPT_FILE | 设置输出文件的位置, 值是一个资源类型, 默认为 STDOUT(浏览器) |
CURLOPT_INFILE | 在上传文件时需要读取的文件地址, 值是一个资源类型 |
CURLOPT_HEADERFUNCTION | 设置一个回调函数, 其有两个参数: 第一个是 cURL 的资源句柄, 第二个是输出的 hander 数据。header 数据的输出必须依赖这个函数, 返回写入的数据大小 |
CURLOPT_WRITEFUNCTION | 拥有两个参数的回调函数: 第一个参数是会话句柄, 第二个是 HTTP 响应头信息的字符串。使用此回调函数, 将自行处理响应头信息。响应头信息是整个字符串。设置返回值为精确的已写入字符串长度。发生错误时传输线程终止 |
PHPRPC 协议
一个轻型的、安全的、跨网际、跨语言的、跨平台的、跨环境的、跨域的协议, 支持复杂对象传输、引用参数传递、内容输出重定向、分级错误处理、会话,是面服务的高性能远程过程调用协议。
PHPRPC 官网: http://phprpc.org/zh_CN/
PHPRPC 作为客户端
<?php
include ("phprpc/phprpc_client.php");
$client = new PHPRPC_Client('http://library.aiyooyoo.com:8080/action/book/rpc.jsp');
echo $client->say('白菜');
echo $client->allcount();
echo $client->findbooks(0,5);
PHPRPC 作为服务端
<?php
include ("phprpc/phprpc_server.php");
class Hello
{
static function HelloWorld()
{
return 'Hello World';
}
}
$server = new PHPRPC_Server();
$server->add('HelloWorld', 'hello');
$server->start();
PHPRPC 客户端调用上面服务端
<?php
include ("phprpc/phprpc_client.php");
$client = new PHPRPC_Client('http://127.0.0.1/php/rtest.php');
echo $client->HelloWorld();
SOAP
Cookie
Cookie 是在远程浏览器端存储数据并以此跟踪和识别用户的机制。Cookie 是存储在客户端上的一小段数据, 浏览器(客户端) 通过 HTTP 协议和服务器进行 Cookie 交互。
关于 Cookie 的 RFC 文档主要有: RFC 6265、 RFC 2109
Cookie 主要是参照 RFC2019 标准由客户端实现其生成、使用整个管理过程, 服务端则参照此标准实现和客户端之间的交互指令。
Cookie 是 HTTP 头的一部分, 即先发送或请求Cookie, 接下来才是data 域,所以setcookie() 等函数必须在其输出数据之前调用, 这和 header() 函数是相同的。也可以使用输出缓冲函数延迟脚本的输出, 直到设置好所有 Cookie 和其他 HTTP 标头。
Cookie 跨域与P3P协议
正常 Cookie 只能在一个应用中共享, 即一个 Cookie 只能由创建它的应用获得。实现 Cookie 的跨域, 主要是为了统一应用平台, 即实现目前流行的单点登录。最简单的方式就是使用 P3P 协议。
P3P(Platform for Prvacy Preferences) 协议由万维网协会研制, 为 Web 用户提供了对自己公开信息的更多控制。
- 页面的 Cookie 不能是浏览器进程的 Cookie, 如不设置超时时间的 Cookie, 则会跨域会取不到
- 利用 IFRAME 时, 记得要在相应的动态页面的页头添加一下 P3P 的信息, 否则 IE 会把 IFRAME 框里的 Cookie 给阻止掉, 产生问题本身未保存, 自然就去不到了。这其实是 FRAMESET 和 Cookie 的问题, 使用 FRAME 或者 IFRAME 时都会遇到
- IE 对跨域访问 Cookie 限制比较严格, 在 Firefox、CHrome 等浏览器下测试, 即使不使用 P3P头信息也能成功
如果是多个工程, 其间关系就变得非常复杂, 这个时候, 就需要一个完整的 SSO 方案, 通常是通过 SSO Server 系统进行中转, CAS 就是一个成熟的 SSO 解决方案。而不是使用跨域 Cookie。
本地存储 localStorage
一个域名的每个 Cookie 限制在4千字节键值对的形式存在, 如果要在本地存储数据, 可以使用localStorage本地存储。
//检测浏览器是否支持 localStorage
if (window.localStorage) {
alert('This browser supports localStorage');
} else {
alert('This browser does not support localStorage');
}
HTML 5 本地存储只能存储字符串, 任何格式存储的时候都会被自动转化为字符串, 所以读取的时候, 需要自己进行类型转换。
HTML 5 监听 storage 键值对改变事件:
if (window.AddEventListener) {
window.addEventListener("storage", handle_storage, false);
} else if(window.attachEvent) {
window.attachEvent("onstorage", handle_storage);
}
function handle_storage(e)
{
if (!e) {
e = window.event;
}
// showStorage();
}
Session
Session 即会话, 指一种持续性的、双向的连接。Session 和 Cookie 本质上没什么区别, 都是针对 HTTP 协议局限性而提出的一种保持客户端和服务器之间保持会话状态的机制。
Session 指用户在浏览某个网站时, 从进入网站到浏览器关闭这段时间内的会话。
要使用 Session 必须在程序的最开始处执行 session_start(), 前面不能有任何输出内容, 否则就会抛出 WARNING 级别错误。
Session 通过一个称为 PHPSESSID 的 Cookie 和服务器联系。Session 是通过 SessionID 判断客户端用户的, 即 Session 文件的文件名。
Session 的回收是被动的, 为了保证过期的 Session 能被正常回收, 可以修改 PHP配置文件中的 session.gc_divisor 参数提高回收率(太大了会增加负载), 或者设置一个变量判断是否过期。
访问量大的网站可以通过使用 session_set_save_handler函数来将 session 存储在 Data Base(数据库、内存表、APC等)中。