amazon web services – WebSockets – send json data via php-ThrowExceptions

Exception or error:

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\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);

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).

How to solve:

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.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));
  }
}

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\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)));
  }
} 

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

Answer:

You need to use PHP websocket client for your requirement. Below is one of the client which can be used for your requirements:

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

Sample 1:

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

Sample 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;
   }
}

Hope this helps.

Answer:

You probably need to use http_build_query(); function like :

$content = http_build_query($content);

and use form post to send message, so try the following code to check if socket connection is success, probably mistake in your code is pfsockopen() should be @fsockopen()

Edit this for your requirements :

$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);

This code works fine in my site as a function with

$out .= “Content-type: application/x-www-form-urlencoded\r\n”; instead of 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
}

Answer:

I would recommend using the cURL extension for PHP. The cURL lib maintains a pool of persistent connections by default.

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

Leave a Reply

Your email address will not be published. Required fields are marked *