I was asked me to make an api call using websocket with php Ratchet at work. Since I'm totally unfamilier with websocket so I googled and watched youtube videos to solve this problem, but The more I searched, the more I felt it is impossible to call api with websocket.
Am I missing something or is it really impossible to call api by websocket?
If it is possible, can you please show me an example how to do it
I know i might sound awkward since I don't have a solid understanding of websockets, English isn't even my first language, but i'm really desperate please help me
A REST API is fundamentally different from a WebSocket API.
REST API
Calls are made through HTTP(S). You can use AJAX (see here: https://en.wikipedia.org/wiki/Ajax_(programming)) to access HTTP endpoints directly from the browser. In JavaScript you would use the Fetch-API (see here: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make calls. Each and every call is stateless per definition. Cookies and special headers must be send with every request to create a context (e.g. user that is logged in).
Example (Client):
fetch('http://example.com/my-rest-api/endpoint1')
.then(response => response.json())
.then(data => console.log(data));
Example (Server):
app.get('/my-rest-api/endpoint1', handlerFunc);
WebSocket API
A stateful connection must be established between a client and a server. The client and server can exchange messages via the connection. Unlike a REST-API messages can be send bidirectional.
A high-level implementation of the WebSocket API is Socket.io.
An API can be designed by defining message types with payloads.
I also would not recommend to use PHP for a WS-API (even though there is a solution like Ratchet). Use a language/runtime that was developed for event-based use cases like this (e.g. nodeJS).
Example (Client):
const socket = SocketClass('example.com/my-websocket-api');
// variant 1 with callbacks
const response = socket.sendMessage('endpoint1', myData);
// variant 2 with message listener
socket.on('endpoint1Response', handlerFunc);
socket.sendMessage('endpoint1', myData);
Example (Server):
const serverSocket = SocketClass('/my-websocket-api');
serverSocket.on('connection', function (socket) {
// variant 1 with callbacks
socket.on('endpoint1', (data, callback) => {
callback(null, responseData);
});
// variant 2 with message listener
socket.on('endpoint1', (data, cb) => {
socket.emit('endpoint1Answer', responseData);
});
});
Related
I have a simple rest endpoint built on PHP that works on postman and browsers.
https://someserver.com/api/endpoint.php?name=hello
But the code fails to execute when invoked from Swift.
Upon Investigation, I found out that Postman and Browser sends the Host Header whereas the Swift URLRequest doesn't. When you don't send the HOST Header, the php server refuses to accept the incoming request and I believe the request is rejected at the web server level. I tried using the
request.addValue("127.0.0.1", "Host")
but it still didn't work. Any help is appreciated.
Full Code (Swift 4)
let url = urlInput.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
print("URL is: " + url)
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "GET"
request.addValue(<#T##value: String##String#>, forHTTPHeaderField: <#T##String#>)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
if let httpResponse = response as? HTTPURLResponse {
let respStr = String(data: data!, encoding: String.Encoding.utf8) as String?
print ("Response is \(respStr ?? "")")
print("statusCode: \(httpResponse.statusCode)")
let data: Data? = respStr?.data(using: .utf8)
}
})
Swift is ignoring the fact that you set the header, as it typically should. See the docs about Swift headers and why it's ignoring your Host header.
The URL Loading System handles various aspects of the HTTP protocol
for you (HTTP 1.1 persistent connections, proxies, authentication,
and so on). As part of this support, the URL Loading System takes
responsibility for certain HTTP headers:
Content-Length
Authorization
Connection
Host
Proxy-Authenticate
Proxy-Authorization
WWW-Authenticate
If you set a value for one of these reserved headers, the system may
ignore the value you set, or overwrite it with its own value, or
simply not send it. Moreover, the exact behavior may change over
time. To avoid confusing problems like this, do not set these headers
directly.
The documentation is here:
https://developer.apple.com/documentation/foundation/nsurlrequest#1776617
I have seen this before with Java - there is a workaround in Java depending on the library you are using, but I don't know about Swift.
I figured out that the issue happens only when you hit a php server. The php web server looks for some header and cant find it when the call is made from Swift. I even copied the working code from Postman and results were the same.
The solution I went with was to create a proxy server using firebase cloud function. Swift code would hit a firebase cloud function which talks to php and sends the response to swift.
I want to subscribe the Spring framework WebSocket and receive the reply.
According to my target WebSocket server, the communication is done using STOMP publish protocol (Build based on Java Springframework API) https://stomp.github.io/stomp-specification-1.1.html
Now the client that I am working on, is build based on PHP and using https://github.com/Textalk/websocket-php/ for websocket client.
My idea to receive the server response is to follow the STOMP over Websocket technique based on this guy's answer Websocket Client not receiving any messages.
Using the current websocket client, I perform these steps
Send Connection (request?)
Send Subscription
Actively receive the reply
$client = new WebSocket\Client($ws_url);
//Step 1 Inintate connection;
$open_msg = "CONNECT\naccept-version:1.0,1.1,2.0\n\n\x00\n";
//Step 2 Subscribe Request;
$client->send($open_msg);
$subs = "SUBSCRIBE\nid:0\ndestination:/user/queue\nack:auto\n\n\x00\n";
$client->send($subs);
while (true) {
try {
$message = $client->receive();
echo $message;
// Act[enter image description here][4] on received message
// Later, Break while loop to stop listening
} catch (\WebSocket\ConnectionException $e) {
// Possibly log errors
}
}
$client->close();
The connection (Step 1) is done and tested.
current send and receive result image
Since it is running on the loop, the Received is always printed.
Does anyone know why the API did not send reply?
It turns out, I have to implement the other Websocket library instead
Instead of using https://github.com/Textalk/websocket-php/ , I moved on and use https://github.com/ratchetphp/Pawl
I don't know what just happened. But I think Textalk is synchronous websocket library and ratchet is asynchronous websocket library.
My current hypothesis is whenever you want to do Stomp over websocket, make sure
Send Connection message ("CONNECT\naccept-version:1.0,1.1,2.0\n\n\x00\n")
Send subscription ("SUBSCRIBE\nid:0\ndestination:/user/queue\nack:auto\n\n\x00\n")
Use the asynchronous Websocket instead of synchronous one
Have a nice day
I'm trying to forward a request made from the client for a stream, where it keeps the requests originally made from the video player intact:
Content-Type: Keep-Alive;
Range: 0-
...
What I'm Using:
Frontend: Web - ReactJS
Backend: PHP REST API
CDN: AWS CloudFront
Storage: AWS S3
Architecture Graphic
Reason:
I need to be able to authenticate the user with our own JWT middleware through the REST to validate if they can access the file.
Constraints:
Cannot use nginx to forward the request, unless there is still a way to authenticate it with the PHP Middleware.
What I've Looked Into:
aws php sdk
I've look at the AWS PHP, but the documentation on this specific functionality seems to be missing.
guzzle + php curl
I'm afraid my knowledge is lacking in terms of what I would need to pass onto the CloudFront for this to work.
cloudfront signed url/signature
Unless I'm mistaken, this would not be helpful because the video expiration for access would be set by AWS and not by the App's REST API, so if they refresh their JWT it would not be updated with the signature.
why not s3 directly?
S3 doesn't support headers for chunks like, Range: 0-100 bytes.
Any help or recommendations would be appreciated, even if it means recommending to buy something pre-built to look at how they implemented it.
======= UPDATE: June 29, 2020 =======
After the recommendation from #ChrisWilliams, I ended up creating a script on AWS Lambda#Edge with the following configurations:
Trigger: CloudFront - viewer request
The reason for viewer request was because it's the only way to get the GET query parameters from the user's original request.
Function Code:
(Please forgive the very rough code to get things working)
File: index.js
// IMPORTS
const zlib = require('zlib');
const https = require('https');
// HTML ERROR TEMPLATE
const content = `
<\!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Unauthorized Page</title>
</head>
<body>
<p>Unauthorized!</p>
</body>
</html>
`;
// TRIGGER FUNCTION
exports.handler = async (event, context, callback) => {
// Getting request and response
const originalResponse = event.Records[0].cf.response;
const request = event.Records[0].cf.request;
// Setup for html page (cont content)
const buffer = zlib.gzipSync(content);
const base64EncodedBody = buffer.toString('base64');
// Response Templates
var response401 = {
headers: {
'content-type': [{key:'Content-Type', value: 'text/html; charset=utf-8'}],
'content-encoding' : [{key:'Content-Encoding', value: 'gzip'}]
},
body: base64EncodedBody,
bodyEncoding: 'base64',
status: '401',
statusDescription: "OK"
};
var response500 = {
headers: {
'content-type': [{key:'Content-Type', value: 'text/html; charset=utf-8'}],
'content-encoding' : [{key:'Content-Encoding', value: 'gzip'}]
},
body: base64EncodedBody,
bodyEncoding: 'base64',
status: '500',
statusDescription: "OK"
};
// Perform Http Request
const response = await new Promise((resolve, reject) => {
// Expected ?token=ey...
const req = https.get(`https://myauthserver.com/?${(request && request.querystring) || ''}`, function(res) {
if (res.statusCode !== 200) {
return reject(response401);
}
return resolve({
status: '200'
});
});
req.on('error', (e) => {
reject(response500);
});
}).catch(error => error);
// Get results from promise
const results = await response;
if (results.status === '200') {
// Successful request - continue with the rest of the process
callback(null, request);
}
// Not successful, show the errors results (401 or 500)
callback(null, results);
};
NOTE: You will have to try this a few times in case any typos or syntax errors arise because of the caching. I also recommend trying this with different IP addresses to validate access to the content. Not to mention you will get scenarios of 502 if the returned request isn't formatted correctly with the base64EncodedBody.
DOUBLE NOTE: This was after looking at the tutorials from AWS that weren't working or outdated and looking at the comments of multiple devs not getting things working.
I would suggest using a Lambda#Edge function rather than adding a third stage in front of your CloudFront.
By adding a proxy in front of your CloudFront it could lead to issues with debug, and allows someone to bypass the proxy to reach your CloudFront origin without locking it down.
Using a Lambda#Edge function guarantees that the solution validates the authenticity of the JWT token, it could be configured to either validate the JWT token with the Lambda function directly or have the Lambda call an endpoint you build to validate. If the JWT is invalid it can reject the request.
Amazon have a great article with a demo stack that demonstrates how you can make use of this.
We're working on a Guzzle 6 based PHP client for a response time sensible case.
Guzzle can do async requests via curl that return a promise API. Because the body can be retrieved as a stream I'm wondering whether the promise resolves (e.g. forced via wait())
a) when the last header is received (like the on_headers hook)?
b) when the body has been fully received?
I tried to find out in the Guzzle sources, but I failed miserably.
The answer is option b
If you're using the default Curl-based calls, the promises don't resolve until after the curl calls are complete. In CurlHandler's __invoke:
curl_exec($easy->handle);
...
return CurlFactory::finish($this, $easy, $this->factory);
which returns:
return new FulfilledPromise($easy->response);
Similar code exists for CurlMultiHandler. The promises aren't resolved in the on_headers functions, so presumably it's when the body is complete. I believe the streams are actually memory/disk-based, not network-based.
And if you think about it, this makes sense; you shouldn't successfully resolve a promise before you know all the data downloaded OK.
I am trying to send json-rpc request to remote server with jquery getJSON method. Here is my code:
json_string=JSON.stringify(obj);
var jqxhr = $.getJSON("https://91.199.226.106/ssljson.php?jsoncallback=?", json_string, function(data){
alert("aaaaaa");
});
jqxhr.error(function() { alert("error"); })
Here is my json-rpc string example:
{"jsonrpc":"2.0","method":"merchant_check","params":[{"hostID":150999,"orderID":116,"amount":"150","currency":"051","mid":15001038,"tid":15531038,"mtpass":"12345","trxnDetails":""}],"id":116}
And here is the error I get:
{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid JSON-RPC 2.0 request error (-32600)"}}
I can`t get what is the issue. Am I doing something wrong? Maybe I need to send request with php and not jquery? Then how should I do it?
The getJSON as the name say will send GET request if you want to use JSON-RPC you need to use POST like:
var json_string = JSON.stringify(obj);
$.post('https://91.199.226.106/ssljson.php', json_string, function(response) {
// process response
}, 'json');
but this will only work if your page is on the same server, unless you use CORS.
That specific error message is supposed to tell you that the message envelope is invalid per the JSON-RPC 2.0 spec, or that there's a parse error in the JSON itself.
Unfortunately, in practice, many services return this error under a much wider variety of circumstances. (e.g.: missing authentication token, etc)
Specific problems with your example message?
Does the web-service accept GET requests? (i.e: should this be a POST instead?)
Does the web-service actually require the ?jsoncallback=? bit? That's normally for a JSONP request rather than JSON-RPC. The service is returning a real JSON-RPC error status, so I'd be really surprised if it needed that GET parameter, and (depending on the web-service configuration) might be interpreted as part of the envelope, which would make it an invalid JSON-RPC request!
Does merchant_check take an array of one-or-more transactions as its only parameter? If not, then you've got the syntax wrong for params. Some services want params to be an Array, some services want it to be an Object. Consult the SMD/documentation to determine which is the case.
The service might require text/json (or something else) as the mime-type for the request.
Recommended Approach:
To avoid these issues, you should probably start by using a purpose-built JSON-RPC library, like the one provided in Dojo toolkit, and use the SMD published by the web-service if it has one. (I recommend against hand-constructing JSON-RPC messages).