按照服务器网络协议解析输入数据流的工具。可自动拆包或合包,处理数据边界,校验输入流数据格式,解析出正确的传输数据。可用于辅助PHP服务器开发。
支持的网络协议格式:TCP,HTTP,WEBSOCKET
解析器列表:
tcp服务器流解析器
Bobby\ServerNetworkProtocol\Tcp\Parser
http服务器数据流解析器
Bobby\ServerNetworkProtocol\Http\Parser
websocket服务器数据帧解析器
Bobby\ServerNetworkProtocol\Websocket\Parser
以上解析器均实现了Bobby\ServerNetworkProtocol\ParserContract接口,暴露以下调用方法:
public function __construct(array $decodeOptions = []);
构造函数,传入解析选项构成解析上下文
参数列表:
$decodeOptions 可选。解析选项。
public function input(string $buffer);
输入需要解析原生字符串:
参数列表:
$buffer 要解析的原生字符串。
public function decode(): array;
解析已输入的原生字符串,返回解析结果数组,如解析出多个合法消息则返回包含多个消息的数组。\
public function clearBuffer();
清除未解析的字符串缓冲区,调用后未解析的字符串将被清除。
public function getBufferLength(): int;
获取剩余尚未解析的字符串长度。
public function getBuffer(): string;
获取剩余尚未解析的字符串。
不同解析器之间的微小不同:
构造函数:
Bobby\ServerNetworkProtocol\Tcp\Parser::__construct(array $decodeOptions = [])
当什么选项都不设置代表不解析数据直接返回原生字符串。可设置选项:
open_eof_split boolean 是否开启结束符检测,和open_length_check只能同时开启其中一个。
package_eof string 消息结束符。根据该结束符检测消息边界。
open_length_check boolean 开启长度检查。
package_length_type string 当open_length_check为true时有效,长度字段打包格式,详见php pack函数。支持的格式有:c,C,s,S,v,V,n,N。
package_length_offset int 当open_length_check为true时有效,长度字段在数据包中的偏移起始位置,从0算起。
package_body_offset int 当open_length_check为true时有效,要截取的消息的偏移起始位置,即截取的消息为substr($package, $package_body_offset, $length)
package_max_length int 每条消息的最大长度。仅当open_eof_split或open_length_check为true时有效。
cat_exceed_package boolean 当消息超过设置的最大长度时候是否裁剪消息。默认值是false。如果值为false,当消息超出最大长度时候将返回null。
Bobby\ServerNetworkProtocol\Http\Parser::__construct(array $decodeOptions = [])
什么都不设置代表无以下限制。可设置选项:
follow_ini boolean 是否根据php.ini配置进行数据解析。
max_package_size int 数据包的最大长度,超出该初度将抛出异常。
Bobby\ServerNetworkProtocol\Websocket\Parser::__construct(array $decodeOptions = [])
没有可用选项设置。
解析方法:
Bobby\ServerNetworkProtocol\Tcp\Parser::decode()
返回经过根据传入构造函数选项解析出来的完整的字符串的数组。
Bobby\ServerNetworkProtocol\Http\Parser::decode()
返回一个Bobby\ServerNetworkProtocol\Http\Request对象数组,对象里包含所有Http请求的相关信息。
Bobby\ServerNetworkProtocol\Http\Request包含以下属性和方法:
public $server;
相当于$_SERVER数组。
public $get;
相当于$_GET数组。
public $post;
相当于$_POST数组。
public $request;
相当于$_REQUEST数组。
public $header;
包含HTTP头信息。
public $cookie;
相当于$_COOKIE数组。
public $files;
相当于$_FILES数组。不同的地方没有tmp_name,多了content项包含着文件内容。
public $rawContent;
获取原始的HTTP body内容,为字符串格式。
public $rawMessage;
获取原始的HTTP数据包内容,为字符串格式。
public $uploadedFileTempName;
调用compressToEnv()方法后生成的$_FILES的临时文件的集合,可用于常驻进程下手动清除临时文件
public function compressToEnv()
将相关属性的值设置到$_SERVER,$_GET,$_POST,$_REQUEST,$GLOBALS以及$_FILES。
Bobby\ServerNetworkProtocol\Websocket\Parser::decode()
返回一个Bobby\ServerNetworkProtocol\Websocket\Frame对象数组。Bobby\ServerNetworkProtocol\Websocket\Frame对象是websocket数据帧的值对象。通过$frame->payloadData可获取传入数据帧承载的数据。
示例:
解析TCP通信数据
$config = [
'open_length_check' => true, // 可选,开启长度检查。开启该选项后以下选项如无特殊说明都是必选选项。
'package_length_type' => 'N', // 长度字段打包格式,详见php pack函数。支持的格式有:c,C,s,S,v,V,n,N
'package_length_offset' => 0, // 长度字段在数据包中的偏移起始位置,从0算起
'package_body_offset' => 4, // 要截取的消息的偏移起始位置,即截取的消息为substr($package, $package_body_offset, $length)
'package_max_length' => 1024, // 可选,是否限制数据包最大长度
'cat_exceed_package' => true // 可选,如果超出数据包长度是否截取数据包,如果值为false,超出数据包最大长度的数据包解析后的结果将为null。默认为false。
];
$parser = new \Bobby\ServerNetworkProtocol\Tcp\Parser($config);
$message = "Hello world\nI am a PHPer!\nYou too.";
$rawData = pack('N', strlen($message)) . $message;
$rawData .= $rawData . (substr($rawData, 0 , 10));
$parser->input($rawData);
var_dump($parser->decode());
// output
/*
array(2) {
[0]=>
string(34) "Hello world
I am a PHPer!
You too."
[1]=>
string(34) "Hello world
I am a PHPer!
You too."
}
*/
$parser->input(substr($rawData, 10));
var_dump($parser->decode());
// output
/*
array(1) {
[0]=>
string(34) "Hello world
I am a PHPer!
You too."
*/
$config2 = [
'open_eof_split' => true,
'package_eof' => "\n"
];
$parser2 = new \Bobby\ServerNetworkProtocol\Tcp\Parser($config2);
$parser2->input($message);
var_dump($parser2->decode());
//output
/*
array(2) {
[0]=>
string(11) "Hello world"
[1]=>
string(13) "I am a PHPer!"
}
*/
解析http form-data消息:
require __DIR__ . '/../../vendor/autoload.php';
$parser = new \Bobby\ServerNetworkProtocol\Http\Parser();
$body =<<
-----------------------------20896060251896012921717172737
Content-Disposition: form-data; name="file[][]"; filename="file1.txt"
Content-Type: text/plain-file1
fghjkltyuikolyuioghjk@#$%^&*sss
-----------------------------20896060251896012921717172737
Content-Disposition: form-data; name="file[2][]"; filename="file2.txt"
Content-Type: text/plain-file2
hello world!
-----------------------------20896060251896012921717172737
Content-Disposition: form-data; name="file[0][]"; filename="file3.txt"
Content-Type: text/plain-file3
I am a phper!
-----------------------------20896060251896012921717172737--
EOF;
$bodyLength = strlen($body);
$buffer =<<
POST /test.html HTTP/1.1
Host: example.org
Content-Length: $bodyLength
Content-Type: multipart/form-data;boundary="---------------------------20896060251896012921717172737"
EOF;
$parser->input($buffer);
$parser->input("\r\n\r\n");
$parser->input($body);
$requests = $parser->decode();
foreach ($requests as $request) {
$request->compressToEnv();
var_dump($_FILES, $_SERVER);
}
//output
/*
* array(1) {
["file"]=>
array(4) {
["name"]=>
array(2) {
[0]=>
array(2) {
[0]=>
string(9) "file1.txt"
[1]=>
string(9) "file3.txt"
}
[2]=>
array(1) {
[0]=>
string(9) "file2.txt"
}
}
["size"]=>
array(2) {
[0]=>
array(2) {
[0]=>
int(31)
[1]=>
int(13)
}
[2]=>
array(1) {
[0]=>
int(12)
}
}
["tmp_name"]=>
array(2) {
[0]=>
array(2) {
[0]=>
string(42) "C:\Users\PC\AppData\Local\Temp\php6B12.tmp"
[1]=>
string(42) "C:\Users\PC\AppData\Local\Temp\php6B13.tmp"
}
[2]=>
array(1) {
[0]=>
string(42) "C:\Users\PC\AppData\Local\Temp\php6B14.tmp"
}
}
["error"]=>
array(2) {
[0]=>
array(2) {
[0]=>
int(0)
[1]=>
int(0)
}
[2]=>
array(1) {
[0]=>
int(0)
}
}
}
}
array(13) {
["REQUEST_METHOD"]=>
string(4) "POST"
["REQUEST_URI"]=>
string(10) "/test.html"
["SERVER_PROTOCOL"]=>
string(3) "1.1"
["REQUEST_TIME"]=>
int(1583903787)
["REQUEST_TIME_FLOAT"]=>
float(1583903787.4804)
["CONTENT_LENGTH"]=>
string(3) "619"
["HTTP_HOST"]=>
string(11) "example.org"
["SERVER_NAME"]=>
string(11) "example.org"
["SERVER_PORT"]=>
string(2) "80"
["HTTP_CONTENT_LENGTH"]=>
string(3) "619"
["HTTP_CONTENT_TYPE"]=>
string(87) "multipart/form-data;boundary="---------------------------20896060251896012921717172737""
["CONTENT_TYPE"]=>
string(19) "multipart/form-data"
["QUERY_STRING"]=>
string(0) ""
}
*/