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.1rn"; $body .= "Host: $hostrn"; $body .= "Content-Type: application/jsonrn"; $body .= "Content-Length: " . strlen($content) . "rn"; $body .= "Connection: Closernrn"; $body .= $content; $body .= "rn"; 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).
Advertisement
Answer
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 />n"; } else { fwrite($fp, "n"); 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.1rnHost: echo.websocket.orgrnAccept: */*rnConnection: UpgradernUpgrade: websocketrnSec-WebSocket-Version: 13rnSec-WebSocket-Key: ".rand(0,999)."rnrn"); 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.1rnHost: stream.binance.com:9443rnAccept: */*rnConnection: UpgradernUpgrade: websocketrnSec-WebSocket-Version: 13rnSec-WebSocket-Key: ".rand(0,999)."rnrn"); 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.