【问题标题】:WebSockets - send json data via phpWebSockets - 通过 php 发送 json 数据
【发布时间】:2020-03-29 14:13:51
【问题描述】:

我正在尝试从 PHP 向我的 websocket aws api 网关发送一个即发即弃的请求。

我设置了一个名为“sendmessage”的操作。

这是我正在使用的代码:

$protocol = "ssl";
$host = "<myendpoint>.amazonaws.com";
$port = 443;
$path = "/<mystage>/";
$timeout = 2000;

$socket = pfsockopen($protocol . "://" . $host, $port,
                    $errno, $errstr, $timeout);

$content = "{'action': 'sendmessage', 'data': 'test'}";
$body = "POST $path HTTP/1.1\r\n";
$body .= "Host: $host\r\n";
$body .= "Content-Type: application/json\r\n";
$body .= "Content-Length: " . strlen($content) . "\r\n";
$body .= "Connection: Close\r\n\r\n";
$body .= $content;
$body .= "\r\n";

fwrite($socket, $body);

但是,什么都没有发生。

如果我使用 wscat,比如:

wscat -c wss://<my-endpoint>.amazonaws.com/<my-stage>

> {'action': 'sendmessage', 'data': 'test'}
>

效果很好。

我在我的 php 代码中做错了什么?

注意:我需要持久的套接字连接(使用 pfsockopen 函数时的方式)。

【问题讨论】:

    标签: php amazon-web-services websocket aws-api-gateway


    【解决方案1】:

    由于您没有提供手点链接,所以这里有一些注释,以下是自己的测试!

    我猜问题出在wss部分,php需要先检索证书,这样才能加密数据。

    您的代码应该可以在 ws:// 流上正常工作。

    要连接到常规的ws:// 流,只需使用fsockopen()

    <?php
    $fp = fsockopen("udp://echo.websocket.org", 13, $errno, $errstr);
    if (!$fp) {
        echo "ERROR: $errno - $errstr<br />\n";
    } else {
        fwrite($fp, "\n");
        echo "Connected!";
        echo fread($fp, 26);
        fclose($fp);
    }
    

    但是要连接到wss:// 安全的 websocket 流,使用 php,没有库,我们需要首先创建一个隧道,通过使用 @ 查询 公钥 987654322@.

    这是一种握手机制。这可以按如下方式完成。

    注意第一个ssl:// 呼叫。这是TLS 1.0 协议。

    <?php  
    $sock = stream_socket_client("ssl://echo.websocket.org:443",$e,$n,30,STREAM_CLIENT_CONNECT,stream_context_create(null));
    if(!$sock){
     echo"[$n]$e".PHP_EOL;
    } else {
      fwrite($sock,"GET / HTTP/1.1\r\nHost: echo.websocket.org\r\nAccept: */*\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: ".rand(0,999)."\r\n\r\n");
      while(!feof($sock)){
        var_dump(fgets($sock,2048));
      }
    }
    

    输出应该是这样的:

    string(44) "HTTP/1.1 101 Web Socket Protocol Handshake"
    string(21) "Connection: Upgrade"
    string(37) "Date: Thu, 12 Dec 2019 04:06:27 GMT"
    string(52) "Sec-WebSocket-Accept: fTYwcEa6D9kJBtghptkz1e9CtBI="
    string(25) "Server: Kaazing Gateway"
    string(20) "Upgrade: websocket"
    

    相同的基本代码,另一个例子,从币安wss://流中提取数据。

    我们也可以使用TLS 1.2 来代替tls:// 握手。适用于大多数服务器。

    <?php
    $sock = stream_socket_client("tls://stream.binance.com:9443",$error,$errnum,30,STREAM_CLIENT_CONNECT,stream_context_create(null));
    if (!$sock) {
        echo "[$errnum] $error" . PHP_EOL;
    } else {
      echo "Connected - Do NOT get rekt!" . PHP_EOL;
      fwrite($sock, "GET /stream?streams=btcusdt@kline_1m HTTP/1.1\r\nHost: stream.binance.com:9443\r\nAccept: */*\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: ".rand(0,999)."\r\n\r\n");
      while (!feof($sock)) {
        var_dump(explode(",",fgets($sock, 512)));
      }
    } 
    

    这是一种从 php.ini 获取仅远程手柄的 ssl RSA 公钥的方法。可用于加速以后的连接。

    <?php
    $opt = [
      "capture_peer_cert" => true,
      "capture_peer_cert_chain" => true
    ];
    $a = stream_context_create(["ssl"=>$opt]);
    $b = stream_socket_client("ssl://stream.binance.com:9443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $a);
    $cont = stream_context_get_params($b);
    $key = openssl_pkey_get_public($cont["options"]["ssl"]["peer_certificate"]);
    $c = openssl_pkey_get_details($key);
    var_dump($c["key"]);
    

    输出类似:

    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhki(...)7aEsFtUNkwM5R5b1mpqzAwqHyvdamJx20bT6SS6
    PYXSr/dv8ak1d4e2Q0nIa1O7l3w0bZZ4wnp5B8Z+tjPd1W8uaZoRO2iVkPMh2yPl
    j0mmtUw1YlfDyutH/t4FlRCDiD4JjdREQGs381/+jbkdjl2SIb1IyNiCdAXA6zsq
    xwIDAQAB
    -----END PUBLIC KEY-----
    

    可能还有其他怪癖,可以肯定的是,我们需要主手点^。很高兴能测试一下。否则祝你好运,关于这个主题的文档非常缺乏。

    这是still a new born protocol(2011 年!)。最好的细节在 RFC 规范中:

    IETF 将 WebSocket 协议标准化为 RFC 6455 在 2011

    关于握手,必须由GET请求发起。

    客户端将发送一个非常标准的 HTTP 请求,其标头 看起来像这样(HTTP 版本必须是 1.1 或更高版本,并且 方法必须是 GET)

    Writing_WebSocket_servers#Client_handshake_request


    简而言之

    如果未加密的 WebSocket 流量通过显式或 不支持 WebSockets 的透明代理服务器,连接 可能会失败。

    WebSocket#Proxy_traversal

    Transport_Layer_Security#Digital_certificates

    【讨论】:

    • 感谢您的精彩回答!我得到了握手来处理您显示的 GET 请求,它似乎连接正确。我现在如何通过套接字发送数据?我尝试了一个 POST 请求(就像我上面的问题一样)并收到了 400 错误请求。
    • 该 api 只是我使用标准 API Gateway Websockets 端点快速设置的一个测试。因此,我没有自定义文档。坦率地说,AWS 上的 websocket 文档很糟糕。我一直在尝试设置另一个测试端点来回显传递给它的任何内容...
    【解决方案2】:

    您可能需要使用http_build_query(); 函数,例如:

    $content = http_build_query($content);
    

    并使用表单 post 发送消息,因此请尝试以下代码来检查套接字连接是否成功,您的代码中的错误可能是 pfsockopen() 应该是 @fsockopen()

    根据您的要求进行编辑:

    $protocol = "ssl";
    $host = "<myendpoint>.amazonaws.com";
    $port = 443;
    $path = "/<mystage>/";
    $timeout = 2000;
    
    $socket = @fsockopen($protocol . "://" . $host, $port,
                $errno, $errstr, $timeout);
    
    if($socket === false) { return false; };
    
    $content = "{'action': 'sendmessage', 'data': 'test'}";
    $body  = "POST $path HTTP/1.1\r\n";
    $body .= "Host: $host\r\n";
    $body .= "Referer: yourClass (v.".version() .")\r\n";
    $body .= "Content-type: application/json\r\n";
    $body .= "Content-Length: ".strlen($content)."\r\n";
    $body .= "Connection: Close\r\n\r\n";
    $body .= "$content";
    fwrite($socket, $body);
    fclose($socket);
    

    这段代码在我的网站中作为一个函数在我的网站上运行良好

    $out .= "内容类型:application/x-www-form-urlencoded\r\n";而不是 json

    function flexy_Request($url, $_data) {
        // parse the given URL
        $url = parse_url($url);
        if ($url === false || !isset($url['host']) || !isset($url['path'])) {
            return false;
        }
        // extract host and path:
        $host = $url['host'];
        $path = $url['path'];
        // open a socket connection on port 80
        // use localhost in case of issues with NATs (hairpinning)
        $fp = @fsockopen($host, 80);
    
        if($fp===false) { return false; };
    
        $data = http_build_query($_data);
        $out  = "POST $path HTTP/1.1\r\n";
        $out .= "Host: $host\r\n";
        $out .= "Referer: Myclass (v.". flexy_version() .")\r\n";
        $out .= "Content-type: application/json\r\n";
        $out .= "Content-Length: ".strlen($data)."\r\n";
        $out .= "Connection: Close\r\n\r\n";
        $out .= "$data";
        $number_bytes_sent = fwrite($fp, $out);
        fclose($fp);
        return $number_bytes_sent; // or false on fwrite() error
    }
    

    【讨论】:

      【解决方案3】:

      您需要使用 PHP websocket 客户端来满足您的要求。以下是可满足您要求的客户端之一:

      https://github.com/paragi/PHP-websocket-client

      样本 1:

      if( $sp = websocket_open('echo.websocket.org',80) ) {
        websocket_write($sp,"hello server");
        echo "Server responed with: " . websocket_read($sp,$errstr);
      }
      

      示例 2:

      $headers = ["Cookie: SID=".session_id()];
      $sp = websocket_open('echo.websocket.org',80,$headers,$errstr,16);
      if($sp){
         $bytes_written = websocket_write($sp,"hello server");
         if($bytes_written){
           $data = websocket_read($sp,$errstr);
           echo "Server responed with: " . $errstr ? $errstr : $data;
         }
      }
      

      希望这会有所帮助。

      【讨论】:

      • 感谢您的回答。我将如何修改示例以将 json 数据发送到特定路径?此外,在运行进程时是否会保持打开的连接(如使用 pfsockopen)?
      • 我相信你只是将路径附加到 echo.websocket.org/my-path/。上面的库使用 stream_socket_client 而不是 pfsockopen。此链接比较了两种方法:spudsdesign.com/benchmark/index.php?t=socket1
      • 当我尝试附加路径时,它给了我一个“名称或服务未知”的错误。
      • 我认为你是对的,项目文档也有点不清楚。如果您愿意尝试另一个库,我建议您使用这个工具,它似乎有更好的文档:github.com/arthurkushman/php-wss/blob/…
      • 谢谢你,那个库工作得更好了!但是你知道 WebSocketClient 库是否会重用已经建立的连接吗?
      【解决方案4】:

      我建议使用 PHP 的 cURL 扩展。 cURL 库默认维护一个持久连接池。

      <?php
      $proto     = 'https';
      $host      = 'FQDN';         
      $path      = '/path/to/file';
      
      $post_data_array = array(
       'action'  => 'sendmessage',
       'data'    => 'test',
      );
      
      $payload = json_encode($post_data_array);
      
      $ch = curl_init();
      
      curl_setopt($ch, CURLOPT_URL, $proto.'://'.$host.$path);
      curl_setopt($ch, CURLOPT_HTTPHEADER, array(
          'Content-Type: application/json',
          'Content-Length: ' . strlen($payload),
          'Connection: Keep-Alive',
          'Keep-Alive: 300',
        )
      );
      curl_setopt($ch, CURLOPT_POST, 1);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      
      curl_exec($ch);           
      
      curl_close($ch);
      

      【讨论】:

      • 感谢您的回答。端点给了我一个 403 禁止错误。我该如何调查问题所在?
      • 请忽略我对您评论的回复。这是不正确的。为了解决这个新问题,我想向您推荐另一个 SO 帖子:stackoverflow.com/questions/18447454/… 如果这不能帮助解决您的问题,我建议您使用新问题的具体细节打开一个新问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-03
      • 2015-04-12
      • 2012-11-16
      • 2015-11-09
      • 2017-12-03
      • 1970-01-01
      • 2014-11-30
      相关资源
      最近更新 更多