From 99bb88be00f832c58b5fdd467a8a2473830a6547 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Wed, 10 Jun 2020 17:50:35 -0400 Subject: [PATCH] examples: add a WebSocket example (#218) There is nothing really to do in order to support events over websockets. Since a `CloudEvent` can easily be represented in full with JSON, it can be sent over a websocket as `event.toString()`. This example illustrates sending a `CloudEvent` over websocket from a browser or CLI. Fixes: https://github.com/cloudevents/sdk-javascript/issues/156 Signed-off-by: Lance Ball --- examples/websocket/README.md | 45 +++++++++++++++++++++++++ examples/websocket/client.js | 46 +++++++++++++++++++++++++ examples/websocket/index.html | 59 +++++++++++++++++++++++++++++++++ examples/websocket/package.json | 22 ++++++++++++ examples/websocket/server.js | 45 +++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 examples/websocket/README.md create mode 100644 examples/websocket/client.js create mode 100644 examples/websocket/index.html create mode 100644 examples/websocket/package.json create mode 100644 examples/websocket/server.js diff --git a/examples/websocket/README.md b/examples/websocket/README.md new file mode 100644 index 00000000..8c5b7d2d --- /dev/null +++ b/examples/websocket/README.md @@ -0,0 +1,45 @@ +# WebSocket Example + +This example shows how simple it is to use CloudEvents over a websocket +connection. The code here shows backend communication from two server +side processes, and also between a browser and a server process. + +## Running the Example + +This simple project consists of a server and a client. The server receives +`CloudEvents` from the client over a local websocket connection. + + +To get started, first install dependencies. + +```sh +npm install +``` + +### Server +The server opens a websocket and waits for incoming connections. It expects that any +messages it receives will be a CloudEvent. When received, it reads the data field, +expecting a zip code. It then fetches the current weather for that zip code and +responds with a CloudEvent containing the body of the Weather API response as the +event data. + +You will need to change one line in the `server.js` file and provide your Open +Weather API key. + +To start the server, run `node server.js`. + +### Client +Upon start, the client prompts a user for a zip code, then sends a CloudEvent over +a websocket to the server with the provided zip code as the event data. The server +fetches the current weather for that zip code and returns it as a CloudEvent. The +client extracts the data and prints the current weather to the console. + +To start the client, run `node client.js` + +### Browser +Open the [`index.html`]('./index.html') file in your browser and provide a zip +code in the provided form field. The browser will send the zip code in the data +field of a CloudEvent over a websocket. When it receives a response from the server +it prints the weather, or an error message, to the screen. + +To terminate the client or server, type CTL-C. diff --git a/examples/websocket/client.js b/examples/websocket/client.js new file mode 100644 index 00000000..ea4c39f3 --- /dev/null +++ b/examples/websocket/client.js @@ -0,0 +1,46 @@ +/* eslint-disable no-console */ +const readline = require("readline"); +const WebSocket = require("ws"); +const ws = new WebSocket("ws://localhost:8080"); + +const { CloudEvent } = require("cloudevents-sdk"); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +rl.on("close", (_) => console.log("\n\nConnection closed! Press CTL-C to exit.")); + +ws.on("message", function incoming(message) { + const event = new CloudEvent(JSON.parse(message)); + if (event.type === "weather.error") { + console.error(`Error: ${event.data}`); + } else { + print(event.data); + } + ask(); +}); + +function ask() { + rl.question("Would you like to see the current weather? Provide a zip code: ", function (zip) { + console.log("Fetching weather data from server..."); + ws.send(new CloudEvent({ + type: "weather.query", + source: "/weather.client", + data: zip + }).toString()); + }); +} + +function print(data) { + data = JSON.parse(data); + console.log(` +Current weather for ${data.name}: ${data.weather[0].main} +------------------------------------------ +With ${data.weather[0].description}, the temperature is ${Math.round(data.main.temp)}F +and the wind is blowing at ${Math.round(data.wind.speed)}mph. +`); +} + +ask(); diff --git a/examples/websocket/index.html b/examples/websocket/index.html new file mode 100644 index 00000000..4a62c4dc --- /dev/null +++ b/examples/websocket/index.html @@ -0,0 +1,59 @@ + + + + CloudEvent Example + + + + +

Weather By Zip Code

+

Please provide a zip code + +

+

+

+ + \ No newline at end of file diff --git a/examples/websocket/package.json b/examples/websocket/package.json new file mode 100644 index 00000000..155f77a1 --- /dev/null +++ b/examples/websocket/package.json @@ -0,0 +1,22 @@ +{ + "name": "websocket-cloudevents", + "version": "0.0.1", + "description": "An example application that sends and receives CloudEvents over a websocket", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "keywords": [ + "cloudevents", + "example", + "websocket" + ], + "author": "", + "license": "ISC", + "dependencies": { + "cloudevents-sdk": "^2.0.2", + "got": "^11.3.0", + "ws": "^7.3.0" + } +} diff --git a/examples/websocket/server.js b/examples/websocket/server.js new file mode 100644 index 00000000..53ba4a79 --- /dev/null +++ b/examples/websocket/server.js @@ -0,0 +1,45 @@ +/* eslint-disable no-console */ +const got = require("got"); + +const { CloudEvent } = require("cloudevents-sdk"); +const WebSocket = require("ws"); +const wss = new WebSocket.Server({ port: 8080 }); + +const api = "https://api.openweathermap.org/data/2.5/weather"; +const key = "REPLACE WITH API KEY"; + +console.log("WebSocket server started. Waiting for events."); + +wss.on("connection", function connection(ws) { + console.log("Connection received"); + ws.on("message", function incoming(message) { + const event = new CloudEvent(JSON.parse(message)); + console.log(`Message received: ${event.toString()}`); + fetch(event.data) + .then((weather) => { + ws.send(new CloudEvent({ + dataContentType: "application/json", + type: "current.weather", + source: "/weather.server", + data: weather + }).toString()); + }) + .catch((err) => { + console.error(err); + ws.send(new CloudEvent({ + type: "weather.error", + source: "/weather.server", + data: err.toString() + }).toString()); + }); + }); +}); + +function fetch(zip) { + const query = `${api}?zip=${zip}&appid=${key}&units=imperial`; + return new Promise((resolve, reject) => { + got(query) + .then((response) => resolve(response.body)) + .catch((err) => reject(err.message)); + }); +}