Jinyun's Notes

没什么天赋,爱好也不多,但愿坚持做些喜欢的事情

0%

PHP 中发送 HTTP 请求的方式

通常情况下,我们发送 HTTP 请求采用 PHP 封装的 cURL 扩展函数来实现。

cURL 方式

扩展函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<?php

// 初始化一个 cURL 资源对象。
$curl = curl_init();

$options = [
// True 将 curl_exec() 获取的信息以字符串返回,而不是直接输出。
CURLOPT_RETURNTRANSFER => true,

// 禁用时不会将头文件的信息作为数据流输出。
CURLOPT_HEADER => false,

// True 时将会根据服务器返回 HTTP 头中的 "Location: " 重定向。
CURLOPT_FOLLOWLOCATION => true,

// 如果为空字符串 "",会发送所有支持的编码类型。
CURLOPT_ENCODING => '',

// 在 HTTP 请求中包含一个 "User-Agent: " 头的字符串。
CURLOPT_USERAGENT => 'spider',

// True 时将根据 Location: 重定向时,自动设置 Header 中的 Referer: 信息。
CURLOPT_AUTOREFERER => true,

// 在尝试连接时等待的秒数。设置为 0,则无限等待。
CURLOPT_CONNECTTIMEOUT => 120,

// 允许 cURL 函数执行的最长秒数。
CURLOPT_TIMEOUT => 120,

// 指定最多的 HTTP 重定向次数,这个选项是和 CURLOPT_FOLLOWLOCATION 一起使用的。
CURLOPT_MAXREDIRS => 10,

// 全部数据使用 HTTP 协议中的 "POST" 操作来发送。
CURLOPT_POSTFIELDS => 'phone=your-phone&password=your-password',

// True 时会发送 POST 请求,类型为:application/x-www-form-urlencoded,是 HTML 表单提交时最常见的一种。
CURLOPT_POST => true,

// 启用 cURL 验证对等证书。
CURLOPT_SSL_VERIFYPEER => true,

// 不检查服务器 SSL 证书中的公用名。
CURLOPT_SSL_VERIFYHOST => 0,

// False 禁止 cURL 验证对等证书。
CURLOPT_SSL_VERIFYPEER => false,

// True 会输出所有的信息,写入到 STDERR,或在 CURLOPT_STDERR 中指定的文件。
CURLOPT_VERBOSE => true,

// 设置你需要抓取的 URL。
CURLOPT_URL => 'http://www.example.com/users/login',
];


// 设置多个 cURL 传输选项。
curl_setopt_array($curl, $options);

// 运行 cURL,请求网页。
$content = curl_exec($curl);

$response = [
'errno' => curl_errno($curl),
'errmsg' => curl_error($curl),
'header' => curl_getinfo($curl),
'content' => $content,
];

// 关闭 cURL 请求。
curl_close($curl);

print_r($response);

// 结果
* Trying 101.201.xxx.xxx...
* TCP_NODELAY set
* Connected to www.example.com (101.201.xxx.xxx) port 80 (#0)
> POST /users/login HTTP/1.1
Host: www.example.com
User-Agent: spider
Accept: */*
Accept-Encoding: deflate, gzip
Content-Length: 41
Content-Type: application/x-www-form-urlencoded

* upload completely sent off: 41 out of 41 bytes
array(26) {
["url"]=>
string(37) "http://www.example.com/users/login"
["content_type"]=>
string(24) "text/html; charset=UTF-8"
["http_code"]=>
int(200)
["header_size"]=>
int(463)
["request_size"]=>
int(231)
["filetime"]=>
int(-1)
["ssl_verify_result"]=>
int(0)
["redirect_count"]=>
int(0)
["total_time"]=>
float(0.224392)
["namelookup_time"]=>
float(0.015103)
["connect_time"]=>
float(0.030847)
["pretransfer_time"]=>
float(0.03103)
["size_upload"]=>
float(41)
["size_download"]=>
float(98)
["speed_download"]=>
float(436)
["speed_upload"]=>
float(182)
["download_content_length"]=>
float(-1)
["upload_content_length"]=>
float(41)
["starttransfer_time"]=>
float(0.224266)
["redirect_time"]=>
float(0)
["redirect_url"]=>
string(0) ""
["primary_ip"]=>
string(15) "101.201.xxx.xxx"
["certinfo"]=>
array(0) {
}
["primary_port"]=>
int(80)
["local_ip"]=>
string(14) "192.168.xxx.xxx"
["local_port"]=>
int(52637)
}
< HTTP/1.1 200 OK
< Server: nginx
< Date: Fri, 22 Jun 2018 01:43:45 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Vary: Accept-Encoding
< Set-Cookie: SESSID=pau0p79qdallgaee2kcj963n16; expires=Fri, 22-Jun-2018 02:13:45 GMT; Max-Age=1800; path=/; HttpOnly
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< Pragma: no-cache
< Content-Encoding: gzip
<
* Connection #0 to host www.example.com left intact

Process finished with exit code 0

扩展包实现

这里推荐使用 Guzzle 扩展包,诚实的说,也没有理由不使用它。Guzzle 是一个 PHP HTTP 客户端,可以轻松发送 HTTP 请求,并且可以轻松集成 Web服务。

特性

  • 用于构建查询字符串,POST 请求,流式传输大型上传,流式传输大型下载,使用 HTTP cookie,上传 JSON 数据等的简单接口,等等……
  • 可以使用相同的接口发送同步和异步请求。
  • 为请求,响应和流选择 PSR-7 接口。 这使您可以与 Guzzle 一起使用其他 PSR-7 兼容库。
  • 抽象出底层的 HTTP 传输,允许您编写环境和传输不可知的代码; 即不依赖于 cURLPHP 流,套接字或非阻塞事件循环。
  • 中间件系统允许您增强和组合客户端行为。

安装

1
$ composer require guzzlehttp/guzzle

请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php

require '../vendor/autoload.php';

try {
$client = new \GuzzleHttp\Client();
$request = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
} catch (\GuzzleHttp\Exception\GuzzleException $e) {
echo $e->getMessage();
}

$response = [
'status_code' => $request->getStatusCode(),
'content_type' => $request->getHeaderLine('content-type'),
'body' => $request->getBody(),
];
print_r($response);

// 发送一个异步请求。
$request = new \GuzzleHttp\Psr7\Request('GET', 'https://www.baidu.com');
$promise = $client->sendAsync($request)->then(function ($response) {
echo PHP_EOL, 'I completed! ', PHP_EOL, $response->getBody();
});
$promise->wait();

// 结果
Array
(
[status_code] => 200
[content_type] => application/json; charset=utf-8
[body] => GuzzleHttp\Psr7\Stream Object
(
[stream:GuzzleHttp\Psr7\Stream:private] => Resource id #42
[size:GuzzleHttp\Psr7\Stream:private] =>
[seekable:GuzzleHttp\Psr7\Stream:private] => 1
[readable:GuzzleHttp\Psr7\Stream:private] => 1
[writable:GuzzleHttp\Psr7\Stream:private] => 1
[uri:GuzzleHttp\Psr7\Stream:private] => php://temp
[customMetadata:GuzzleHttp\Psr7\Stream:private] => Array
(
)

)

)

I completed!
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>

Stream 方式

Stream 的概述与使用详解

PHP 内置的 Stream 包装类

获取已注册的套接字传输协议列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

print_r(stream_get_transports());

// 结果
Array
(
[0] => tcp
[1] => udp
[2] => unix
[3] => udg
[4] => ssl
[5] => tls
[6] => tlsv1.0
[7] => tlsv1.1
[8] => tlsv1.2
)

获取已注册的流类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

print_r(stream_get_wrappers());

// 结果
Array
(
[0] => https
[1] => ftps
[2] => compress.zlib
[3] => compress.bzip2
[4] => php
[5] => file
[6] => glob
[7] => data
[8] => http
[9] => ftp
[10] => phar
[11] => zip
)

获取已注册的数据流过滤器列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

print_r(stream_get_filters());

// 结果
Array
(
[0] => zlib.*
[1] => bzip2.*
[2] => convert.iconv.*
[3] => string.rot13
[4] => string.toupper
[5] => string.tolower
[6] => string.strip_tags
[7] => convert.*
[8] => consumed
[9] => dechunk
)

流上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

$data = ['phone' => 'your-phone', 'password' => 'your-password', 'sign' => 'your-sign'];
$data = http_build_query($data);
$options = [
'http' => [
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded\r\nContent-length:' . mb_strlen($data) . "\r\n",
'content' => $data,
],
];
$context = stream_context_create($options);
$result = file_get_contents('http://www.example.com/users/login', false, $context);
$result = json_decode($result, true);

print_r($result);

// 结果
Array
(
[status] =>
[msg] => 账号不存在或被禁用
[data] => Array
(
)
)

流过滤器

1
2
3
4
5
$ touch test.txt
$ vim test.txt
HELLO
WORLD
THIS IS A TEST
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$handle = fopen('./test.txt', 'rb');
stream_filter_append($handle, 'string.tolower');
while (feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);

// 结果
hello
world
this is a test

Socket 方式

简单请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

$fp = fsockopen('www.baidu.com', 80, $errno, $errstr);

if (! $fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.baidu.com\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);

while (! feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}

// 结果
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 14615
Content-Type: text/html
Date: Fri, 22 Jun 2018 03:07:09 GMT
Last-Modified: Mon, 11 Jun 2018 11:19:00 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BAIDUID=CB118091B64A7DB3972039E431BB631D:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=CB118091B64A7DB3972039E431BB631D; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1529636829; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
Connection: close

<!DOCTYPE html><!--STATUS OK-->
<html>
<head>...</head>
<body>...</body>
</html>

简单通信

创建 TCP Socket 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php

error_reporting(E_ALL);

set_time_limit(0);

ob_implicit_flush();

$host = '127.0.0.1';
$port = 9004;

// 创建一个 TCP Socket。
$socket = socket_create(
AF_INET,
SOCK_STREAM,
SOL_TCP
) or die('socket_create() failed: ' . socket_strerror(socket_last_error()));

// 在套接字资源上设置阻塞模式。
socket_set_block($socket) or die('socket_set_block() failed: ' . socket_strerror(socket_last_error($socket)));

// 给套接字绑定名字。
socket_bind($socket, $host, $port) or die('socket_bind() failed: ' . socket_strerror(socket_last_error($socket)));

// 侦听套接字上的连接。
socket_listen($socket, 5) or die('socket_listen() failed: ' . socket_strerror(socket_last_error($socket)));

echo "Listening the socket on $host:$port ... ", PHP_EOL;

$clientId = 0;

while (true) {
if (($client = socket_accept($socket)) < 0) {
echo 'socket_accept() failed: ' . socket_strerror(socket_last_error($client));
break;
}

$clientId++;
echo 'Client #' . $clientId . ': Connect', PHP_EOL;

$welcome = "\nWelcome to the PHP Socket Server. \n" .
"To quit client type 'quit'. To shut down the server type 'shutdown'.\n";
socket_write($client, $welcome, strlen($welcome));

$curBuf = '';
while (true) {
if (false === ($buf = socket_read($client, 2048))) {
echo 'socket_read() failed: ' . socket_strerror(socket_last_error($client)) . PHP_EOL;
break 2;
}

if ($buf === "\r\n") {
if ($curBuf === "quit\r\n") {
echo 'Client #' . $clientId . ': Disconnect', PHP_EOL;
break;
} elseif ($curBuf === "shutdown\r\n") {
echo 'Client #' . $clientId . ': Shutdown server', PHP_EOL;
socket_close($client);
break 2;
} else {
$buffer = 'Output: ' . ucwords(str_replace("\r\n", ' ', $curBuf)) . PHP_EOL;
socket_write($client, $buffer, strlen($buffer));
}
echo 'Client #' . $clientId . ': ' . $curBuf . PHP_EOL;
$curBuf = '';
} else {
$curBuf .= $buf;
}
}
socket_close($client);
}
socket_close($socket);

运行:

1
2
3
4
5
6
7
$ php server.php

// 重启一个终端窗口,安装 telnet
$ brew install telnet

// 使用 telnet 进行测试
$ telnet 127.0.0.1 9004
本笔记是笔者在学习和工作中的一些整理,如对您有用,请鼓励我继续写作