bobby/server-network-protocol

Server network protocol parsers

dev-master 2020-06-12 02:03 UTC

This package is auto-updated.

Last update: 2024-11-12 11:57:22 UTC


README

支持的网络协议格式: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消息:

<?php
require __DIR__ . '/../../vendor/autoload.php';

$parser = new \Bobby\ServerNetworkProtocol\Http\Parser();

$body = <<<EOF
-----------------------------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 = <<<EOF
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) ""
  }
  

 */