WebSockets - 通过 php 发送 json 数据

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

我设置了一个名为sendmessage"的操作.

这是我正在使用的代码:

$protocol = "ssl";$host = ".amazonaws.com";$端口 = 443;$path = "//";$超时= 2000;$socket = pfsockopen($protocol . "://" . $host, $port,$errno, $errstr, $timeout);$content = "{'action': 'sendmessage', 'data': 'test'}";$body = "POST $path HTTP/1.1
";$body .= "主机:$host
";$body .= "内容类型:应用程序/json
";$body .= "内容长度:" .strlen($content) ."
";$body .= "连接:关闭

";$body .= $content;$body .= "
";fwrite($socket, $body);

然而,什么也没发生.

如果我使用 wscat,例如:

wscat -c wss://.amazonaws.com/>{'动作':'发送消息','数据':'测试'}>

效果很好.

我的 php 代码做错了什么?

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

解决方案

由于你没有提供handpoint链接,这里有一些注意事项,请自行测试!

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

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

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


但是要连接到 wss:// secure websocket 流,使用 php,没有库,我们需要先创建一个隧道,通过查询 公钥与stream_socket_client.

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

注意第一个 ssl:// 调用.这是TLS 1.0 协议.

输出应如下所示:

string(44) HTTP/1.1 101 Web Socket 协议握手"string(21) 连接:升级"string(37) 日期:2019 年 12 月 12 日星期四 04:06:27 GMT"string(52) "Sec-WebSocket-Accept: fTYwcEa6D9kJBtghptkz1e9CtBI="string(25) 服务器:Kaazing 网关";string(20) 升级:websocket";


相同的基本代码,另一个示例,从 Binance wss:// 流中提取数据.

我们还可以使用带有 tls 的 TLS 1.2:// 握手代替.适用于大多数服务器.


这是一种从 php 检索仅远程手持设备的 ssl RSA 公钥的方法.可用于加速以后的连接.

真的,capture_peer_cert_chain"=>真的];$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"]);

输出类似:

-----开始公钥--MIIBIjANBgkqhki(...)7aEsFtUNkwM5R5b1mpqzAwqHyvdamJx20bT6SS6PYXSr/dv8ak1d4e2Q0nIa1O7l3w0bZZ4wnp5B8Z+tjPd1W8uaZoRO2iVkPMh2yPlj0mmtUw1YlfDyutH/t4FlRCDiD4JjdREQGs381/+jbkdjl2SIb1IyNiCdAXA6zsqxwIDAQAB-----结束公钥-----

可能还有其他怪癖,当然,我们需要主要的handpoint^.很高兴测试一下.否则祝你好运,关于这个主题的文档非常缺乏.

这是仍然是一个新生的协议(2011 年!).最好的细节在 RFC 规范中:

<块引用>

WebSocket 协议由 IETF 标准化为 RFC 64552011

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

<块引用>

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

Writing_WebSocket_servers#Client_handshake_request


简而言之:

<块引用>

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

WebSocket#Proxy_traversal

Transport_Layer_Security#Digital_certificates

I'm trying to send a fire-and-forget request from PHP to my websocket aws api gateway.

I've set up an action called "sendmessage".

This is the code I'm using:

$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
";
$body .= "Host: $host
";
$body .= "Content-Type: application/json
";
$body .= "Content-Length: " . strlen($content) . "
";
$body .= "Connection: Close

";
$body .= $content;
$body .= "
";

fwrite($socket, $body);

However, nothing happens.

If I use wscat, like:

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

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

it works just fine.

What am I doing wrong in my php code?

Note: I need the socket connection to be persistent (the way it is when using the pfsockopen function).

解决方案

Since you didn't provide a handpoint link, here is some notes, following own tests!

I guess the issue comes from the wss part, php needs to retrieve the certificate first, so it can encrypt the data.

Your code should work just fine on a ws:// stream.

To connect to a regular ws:// stream, one can simply use fsockopen().

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


But to connect to a wss:// secure websocket stream, using php, without libraries, we need to create a tunnel first, by querying the public key with stream_socket_client.

This is a handshake mechanism. This can be done as follow.

Notice the first ssl:// call. This is the TLS 1.0 protocol.

<?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
Host: echo.websocket.org
Accept: */*
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: ".rand(0,999)."

");
  while(!feof($sock)){
    var_dump(fgets($sock,2048));
  }
}

The output should looks like:

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"


Same base code, another example, pulling data from Binance wss:// stream.

We can also use TLS 1.2, with a tls:// handshake instead. Works on most servers.

<?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
Host: stream.binance.com:9443
Accept: */*
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: ".rand(0,999)."

");
  while (!feof($sock)) {
    var_dump(explode(",",fgets($sock, 512)));
  }
} 


Here is a way to retrieve only the ssl RSA public key of a remote handpoint, from php. Can be used to speed up later connections.

<?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"]);

Output something like:

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

There is possibly other quircks, to be sure, we need the main handpoint^. Would be glad to test that. Otherwise good luck, there is a big lack of documentation on the subject.

This is still a new born protocol (2011!). Best details are in the RFC specification:

The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011

About the handshake, it must be initiated by a GET request.

The client will send a pretty standard HTTP request with headers that looks like this (the HTTP version must be 1.1 or greater, and the method must be GET)

Writing_WebSocket_servers#Client_handshake_request


In short:

If unencrypted WebSocket traffic flows through an explicit or a transparent proxy server without WebSockets support, the connection will likely fail.

WebSocket#Proxy_traversal

Transport_Layer_Security#Digital_certificates

相关文章