Technical Articles
How to use WebSockets in the Cloud Foundry Environment in the SAP Cloud Platform
Introduction
WebSockets are a pretty common way to enable applications to communicate fast with each other. Especially for Web Development they are essential, therefore it is important that they work sufficient and secure.
Usually they are straight forward and easy to use. But there is an aspect you need to pay attention to if you want to use them in the Cloud Foundry environment. If you don’t know about it, this might save you some time and nerves in the future.
SSL/TLS Termination
Cloud Foundry uses a mechanism called SSL/TLS-Termination. If you haven’t heard of this term before, don’t worry, you did now. It basically provides a proxy for SSL connection which handles the cryptographic processing and forwards the unencrypted data to the corresponding service.
In short words: You can use a secure WebSocket connection, even though you do not provide one yourself. The same works of course for a HTTPS connection.
What does this mean?
The WebSocket server needs to run on the port 8080 which is opened by Cloud Foundry to the internet.
Your WebSocket client needs to access the plain endpoint of your application without a specified port.
Some examples that do not work:
const url = "wss://websocket-cf.cfapps.sap.hana.ondemand.com:8080"
const url = "wss://websocket-cf.cfapps.sap.hana.ondemand.com:3000"
const url = "wss://websocket-cf.cfapps.sap.hana.ondemand.com:80"
Examples that will work:
const url = "wss://websocket-cf.cfapps.sap.hana.ondemand.com"
const url = "https://websocket-cf.cfapps.sap.hana.ondemand.com"
--> Only if there is not a http-based app running
const url = "wss://websocket-cf.cfapps.sap.hana.ondemand.com:443"
--> 443 is the only accessible port
Now, let’s try this.
Hands-on: WebSockets on Cloud Foundry
First of all you need a current version of NodeJS installed. If this is not the case, please refer to: https://nodejs.org/en/download/
Also you will need a SAP Cloud Platform Account in the Cloud Foundry environment.
Next, we are going to create script running on the Cloud Platform: server.js
const WebSocketServer = require('ws').Server
//We will create the websocket server on the port given by Cloud Foundry --> Port 8080
const ws = new WebSocketServer({ port: process.env.PORT || 8080 });
ws.on('connection', function (socket) {
socket.send('Hi, this is the Echo-Server');
socket.on('message', function (message) {
console.log('Received Message: ' + message);
socket.send('Echo: ' + message);
});
});
The script creates a WebSocket server which is listening for connections on the port specified by Cloud Foundry. Just in case, there is a fallback port specified. Once there is a connection established, the server sends a message to the connected client and waits for incoming messages. When a client sends a message the server responds with an echo.
Cloud Foundry also needs some meta information about the application. For this we need a file called package.json:
{
"name": "websocket_cf",
"version": "1.0.0",
"description": "Basic WebSocket example application for Cloud Foundry on SAP Cloudplatform",
"main": "client.js",
"dependencies": {
"ws": "^4.0.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node client.js"
},
"engines": {
"node": "12.x.x"
}
}
It stores information about how to start the application, what dependencies are needed, etc.
To install the dependencies run the following command:
> npm install
In order to deploy the app on the Cloud Foundry environment we need a manifest.yml:
---
applications:
- name: websocket_cf
command: node server.js
buildpack: https://github.com/cloudfoundry/nodejs-buildpack
health-check-type: none
memory: 256M
random-route: true
Now you are ready to deploy the server script to the cloud!
For the next steps you need to have the Cloud Foundry Command Line Interface installed: https://github.com/cloudfoundry/cli#downloads
Now use your command line (CMD, Terminal, …) to navigate to the folder you created the files server.js, package.json and manifest.yml.
> cd /folder/to/the/manifest/
Now set the API Endpoint according to the Cloud Foundry environment you are using. In my case it is: https://api.cf.sap.hana.ondemand.com
> cf api https://api.cf.sap.hana.ondemand.com
api endpoint: https://api.cf.sap.hana.ondemand.com
api version: 2.100.0
Afterwards you need to login using your e-mail and password:
(You might need to choose the corresponding org where you want to deploy the app)
> cf login
After a successful login you can push the app to the cloud:
> cf push
This might take a minute, but once it’s done you should see something like this:
requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: websocket-cf.cfapps.sap.hana.ondemand.com
last uploaded: Tue Jan 23 09:30:51 UTC 2018
stack: cflinuxfs2
buildpack: https://github.com/cloudfoundry/nodejs-buildpack
state since cpu memory disk details
#0 running 2018-01-23 10:31:24 AM 0.0% 15.7M of 256M 56.4M of 1G
The most important information is the url: “urls: websocket-cf.cfapps.sap.hana.ondemand.com”. You will need it to connect the client.
Alright, now that the server is running we need a client to test it: client.js
const WebSocket = require('ws');
//replace the url with yours after pushing the app wss://<your-app>.<your-host>.hana.ondemand.com/
//do not specify a port
//you can use secure websockets because of the SSL-Termination
const url = "wss://websocket-cf.cfapps.sap.hana.ondemand.com"
const ws = new WebSocket(url);
ws.on('open', function() {
const message = 'Hi there!'
ws.send(message);
console.log('Sent Message: '+ message)
});
ws.on('error',function(data){
console.log('Error: ' + data);
});
ws.on('message', function(data, flags) {
console.log('Recieved Message: ' + data);
});
ws.on('close', function() {
console.log('Disconnected from Server');
});
The client connects to the server and sends a message “Hi there”. In case it gets a message from the server, it logs it to console. Same happens if there is an error or the connection is closed. Don’t forget to replace the url with yours.
To start the script run the following command:
> node client.js
You should get the following output:
Sent Message: Hi there!
Recieved Message: Hi, this is the Echo-Server
Recieved Message: Echo: Hi there!
Well that’s it. You have create a simple WebSocket Server and deployed on the SAP Cloud Platform using Cloud Foundry and NodeJS.
If you are lazy, you can also just download the following repository from GitHub, it contains all the necessary files.
For information please refer to:
Thanks, this is really helpful.
Can you share how would the manifest file look in case a separate application on CF (UI Client) needs to communicate to the websocket server.
Hello,
Thank you this is really useful!
Could you please help with some ideas, hints regarding a connection issue.
When the external client tries to connect to my Websocket server, the connection fails and I'm getting the "Upgrade required" - 426 error.
Could you tell me which configuration should be changed, in order to establish the connection.
Thank you in advance.
Regards,
Noemi B.
Hi,
did you use the samples provided here? Or are you using different code snippets?
From the error itself it seems that you are trying to make a plain HTTP Call to the WebSocket server.
https://httpstatuses.com/426
Kind Regards,
Max
Hello,
I used the samples provided here.
Thank you.
Regards,
Noemi B.
Hello,
It is possible to remove the “Upgrade” header in order to establish the connection/handshake or how we can add custom headers to our websocket server ?
Are there any websocket compatible libraries with cloud foundry ?
Regards,
Noemi B
Hello,
I have also used this code as it is presented in the tutorial. An external client tried to connect through TCP on port 443 to the server hosted in SCP and in the logs I see the 426 - Upgrade required error.
The client is a GPS Forwarding tool and is not written by me.
Regards,
George
Hi,
this does not seem to work with Python and Flask. The app does not start.
We use:
port = int(os.environ.get('PORT', 8080))
socketio.run(app, port=port)
On Cloud Foundry we get the following error:
instance: 871f54ea-b0ff-4653-7c84-51f2, index: 0, cell_id: 1aafb73f-4bfc-4187-b6ec-70605827cda2, reason: CRASHED, exit_description: Instance never healthy after 3m0s: Failed to make TCP connection to port 8080: connection refused, crash_count: 1, crash_timestamp: 1551966515081676000, version: 388df7a0-b416-490d-8627-c0003be1c7ce
instance: 871f54ea-b0ff-4653-7c84-51f2, index: 0, cell_id: 1aafb73f-4bfc-4187-b6ec-70605827cda2, exit_description: Instance never healthy after 3m0s: Failed to make TCP connection to port 8080: connection refused, reason: CRASHED
Any help is appreciated.
Hi Marcus,
your current issue is caused by the health check performed by Cloudfoundry. You can fix this by adding "health-check-type: none" to your manifest.yml.
Kind Regards,
Max
Hi Max,
thnks. the error resulted from a missing ” host=’0.0.0.0′ ” in the socketio.run .
Then it works fine.
However, the next issue arises:
We want to secure the app with xsuaa and the approuter.
While I can configure the route for the static files, i wonder how will the xs-app.json for the approuter look like when I want to pass the websockets requests ? In the flask app, only the method for the html template site is secured by the command:
if ‘authorization’ not in request.headers:
abort(403)
However when I run the app on the CF, I see the websocket is never connected.
in html this is never showing status "connected":
Hi author,
I tired to create socket server and connect it on browser. It never works and get 502 error.
I was wondering if the port must be 8080 when creating a websocket server in cloud foundry? I didn't managed to create a socket server with port 8080 coz the port is already in use.
Victor
Hi Victor,
Cloud Foundry only exposes the port 8080 internally for applications so it has to be used. If the port is already in use in your application,do you mean you are running for example an express server on port 8080?
Best regards,
Max
Hi Max,
Yeah, I created an express server but not with the port 8080 and also create a socket server with port 8080
Do u mind if we have a call and I will show u the code?
I tried your sample, and got an error when doing cf push "Error staging application: App staging failed in the buildpack compile phase"
I don't know how to fix it~
Would you be so kind to provide the whole error log? Is this a different error or is it related to your issue before?
No, it's not related to the question above
Excellent work Max ! 🙂
Took me 2 days to figure out that it's impossible to get the server URL via the destinations, and I was including the port like a dummy
Thanks !