How to incorporate websocket into just one route of my Expo App (using Express.js in my backend for routing and REST APIs)

huangapple go评论109阅读模式
英文:

How to incorporate websocket into just one route of my Expo App (using Express.js in my backend for routing and REST APIs)

问题

我有一个基于Expo React Native的前端,使用Node.js与Express.js的后端与Azure IoT Hub进行交互。前端使用REST API。

我的应用程序有多个页面,其中一个页面需要从我的IoT Hub获取实时数据。我在网上阅读到REST API不适合实时数据,最佳选择是使用WebSocket。

我正在使用Azure示例中的此代码来连接到Azure上的我的终端,但我不确定如何将其合并到我的server.js或前端中。我仍然希望在其余页面使用REST API。

我不太确定这个路由的WebSocket应该如何与我的其余路由和页面关联。它是否影响服务器如何处理其余页面?我应该如何将其整合进去?

server.js:

const express = require('express');
const cors = require('cors');
const http = require('http');
const WebSocket = require('ws');
const EventHubReader = require('./EventHubReader'); // 请确保引入正确的EventHubReader模块

const PORT = 3000;
const app = express();

app.use(express.json());
app.use(cors());

app.get('/home', (req, res) => {
  // 只有在这个页面上我需要实时数据
});

app.get('/login', (req, res) => {
  // 登录页面的处理
});

app.post('/control', (req, res) => {
  // 控制页面的处理
});

app.post('/configure', (req, res) => {
  // 配置页面的处理
});

app.post('/settings', (req, res) => {
  // 设置页面的处理
});

app.listen(PORT, () => {
  console.log("Server Listening on PORT:", PORT);
});

const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

wss.broadcast = (data) => {
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      try {
        console.log(`Broadcasting data ${data}`);
        client.send(data);
      } catch (e) {
        console.error(e);
      }
    }
  });
};

server.listen(PORT, () => {
  console.log('Listening on %d.', server.address().port);
});

// 创建一个EventHubReader实例,用于连接到IoT Hub并监听实时数据
const iotHubConnectionString = 'your_iot_hub_connection_string';
const eventHubConsumerGroup = 'your_event_hub_consumer_group';
const eventHubReader = new EventHubReader(iotHubConnectionString, eventHubConsumerGroup);

(async () => {
  await eventHubReader.startReadMessage((message, date, deviceId) => {
    try {
      const payload = {
        IotData: message,
        MessageDate: date || Date.now().toISOString(),
        DeviceId: deviceId,
      };

      wss.broadcast(JSON.stringify(payload));
    } catch (err) {
      console.error('Error broadcasting: [%s] from [%s].', err, message);
    }
  });
})().catch();

home.js:

// 如何与后端建立WebSocket连接?

上述代码示例中,WebSocket服务器通过WebSocket协议与前端建立连接,并在收到数据时广播给所有客户端。在server.js中,我们通过EventHubReader连接到IoT Hub,以侦听实时数据,并将其广播给WebSocket客户端。

home.js或其他页面中,您可以使用WebSocket客户端库(如socket.ioWebSocket库)来建立与后端的WebSocket连接,以接收实时数据。您需要编写前端代码来处理WebSocket连接和接收数据的逻辑。

请确保将your_iot_hub_connection_stringyour_event_hub_consumer_group替换为您自己的IoT Hub连接字符串和事件中心的消费者组名称。

希望这可以帮助您理解如何将WebSocket与其余路由和页面整合到您的应用程序中。如果您有更多问题,请随时提问。

英文:

I have a frontend built on Expo React Native and I am using Nod.js to interact with my Azure IoT Hub on my backend with Express.js to do routing. I am using REST APIs from the frontend.

My application has multiple pages, and on one of the pages, I need to be able to get real-time data from my IoT Hub. I read online that REST APIs are not suited for real-time data and that a websocket is the best option.

I am using this code from an Azure sample to connect to my endpoint on Azure, but I am not sure how to incorporate this into my server.js or my frontend. I still want to use REST APIs for the rest of my pages.

iothubConnectionStringWebsockets.js -- adapted from the link and this

const crypto = require("crypto");
const { Buffer } = require("buffer");
const { Connection, ReceiverEvents, parseConnectionString } = require("rhea-promise");
const rheaPromise = require("rhea-promise");
const { EventHubConsumerClient, earliestEventPosition } = require("@azure/event-hubs");
const WebSocket = require("ws");
// Load the .env file if it exists
require("dotenv").config();
/**
* Type guard for AmqpError.
* @param err - An unknown error.
*/
function isAmqpError(err) {
return rheaPromise.isAmqpError(err);
}
// This code is modified from https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-security#security-tokens.
function generateSasToken(resourceUri, signingKey, policyName, expiresInMins) {
resourceUri = encodeURIComponent(resourceUri);
const expiresInSeconds = Math.ceil(Date.now() / 1000 + expiresInMins * 60);
const toSign = resourceUri + "\n" + expiresInSeconds;
// Use the crypto module to create the hmac.
const hmac = crypto.createHmac("sha256", Buffer.from(signingKey, "base64"));
hmac.update(toSign);
const base64UriEncoded = encodeURIComponent(hmac.digest("base64"));
// Construct authorization string.
return `SharedAccessSignature sr=${resourceUri}&sig=${base64UriEncoded}&se=${expiresInSeconds}&skn=${policyName}`;
}
/**
* Converts an IotHub Connection string into an Event Hubs-compatible connection string.
* @param connectionString - An IotHub connection string in the format:
* `"HostName=<your-iot-hub>.azure-devices.net;SharedAccessKeyName=<KeyName>;SharedAccessKey=<Key>"`
* @returns An Event Hubs-compatible connection string in the format:
* `"Endpoint=sb://<hostname>;EntityPath=<your-iot-hub>;SharedAccessKeyName=<KeyName>;SharedAccessKey=<Key>"`
*/
async function convertIotHubToEventHubsConnectionString(connectionString) {
const { HostName, SharedAccessKeyName, SharedAccessKey } =
parseConnectionString(connectionString);
// Verify that the required info is in the connection string.
if (!HostName || !SharedAccessKey || !SharedAccessKeyName) {
throw new Error(`Invalid IotHub connection string.`);
}
//Extract the IotHub name from the hostname.
const [iotHubName] = HostName.split(".");
if (!iotHubName) {
throw new Error(`Unable to extract the IotHub name from the connection string.`);
}
// Generate a token to authenticate to the service.
// The code for generateSasToken can be found at https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-security#security-tokens
const token = generateSasToken(
`${HostName}/messages/events`,
SharedAccessKey,
SharedAccessKeyName,
5 // token expires in 5 minutes
);
const connection = new Connection({
transport: "tls",
host: HostName,
hostname: HostName,
username: `${SharedAccessKeyName}@sas.root.${iotHubName}`,
port: 443,
reconnect: false,
password: token,
webSocketOptions: {
webSocket: WebSocket,
protocol: ["AMQPWSB10"],
url: `wss://${HostName}:${443}/$servicebus/websocket`,
},
});
await connection.open();
// Create the receiver that will trigger a redirect error.
const receiver = await connection.createReceiver({
source: { address: `amqps://${HostName}/messages/events/$management` },
});
return new Promise((resolve, reject) => {
receiver.on(ReceiverEvents.receiverError, (context) => {
const error = context.receiver && context.receiver.error;
if (isAmqpError(error) && error.condition === "amqp:link:redirect") {
const hostname = error.info && error.info.hostname;
const parsedAddress = error.info.address.match(/5671\/(.*)\/\$management/i);
if (!hostname) {
reject(error);
} 
else if (parsedAddress == undefined || (parsedAddress && parsedAddress[1] == undefined)) {
const msg = `Cannot parse the EventHub name from the given address: ${error.info.address} in the error: ` +
`${error.stack}\n${JSON.stringify(error.info)}.\nThe parsed result is: ${JSON.stringify(parsedAddress)}.`;
reject(Error(msg));
} 
else {
const entityPath = parsedAddress[1];
resolve(`Endpoint=sb://${hostname}/;EntityPath=${entityPath};SharedAccessKeyName=${SharedAccessKeyName};SharedAccessKey=${SharedAccessKey}`);
}
} else {
reject(error);
}
connection.close().catch(() => {
/* ignore error */
});
});
});
}
class EventHubReader {
constructor(iotHubConnectionString, consumerGroup) {
this.iotHubConnectionString = iotHubConnectionString;
this.consumerGroup = consumerGroup;
}
async startReadMessage(startReadMessageCallback) {
try {
const eventHubConnectionString = await convertIotHubToEventHubsConnectionString(this.iotHubConnectionString);
const consumerClient = new EventHubConsumerClient(this.consumerGroup, eventHubConnectionString);
console.log('Successfully created the EventHubConsumerClient from IoT Hub event hub-compatible connection string.');
const partitionIds = await consumerClient.getPartitionIds();
console.log('The partition ids are: ', partitionIds);
consumerClient.subscribe({
processEvents: (events, context) => {
for (let i = 0; i < events.length; ++i) {
startReadMessageCallback(
events[i].body,
events[i].enqueuedTimeUtc,
events[i].systemProperties["iothub-connection-device-id"]);
}
},
processError: (err, context) => {
console.error(err.message || err);
}
});
} catch (ex) {
console.error(ex.message || ex);
}
}
// Close connection to Event Hub.
async stopReadMessage() {
const disposeHandlers = [];
this.receiveHandlers.forEach((receiveHandler) => {
disposeHandlers.push(receiveHandler.stop());
});
await Promise.all(disposeHandlers);
this.consumerClient.close();
}
}
module.exports = EventHubReader;

server.js

const express = require('express');
const cors = require('cors');
const PORT = 3000;
const app = express();
app.use(express.json());
app.use(cors());
app.get('/home', (req, res) => {
// I only want to get real time data on this page
});
app.get('/login', (req, res) => {
});
app.post('/control', (req, res) => {
});
app.post('/configure', (req, res) => {
});
app.post('/settings', (req, res) => {
});
app.listen(PORT, () => {
console.log("Server Listening on PORT:", PORT);
});
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.broadcast = (data) => {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
try {
console.log(`Broadcasting data ${data}`);
client.send(data);
} catch (e) {
console.error(e);
}
}
});
};
server.listen(PORT, () => {
console.log('Listening on %d.', server.address().port);
});
const eventHubReader = new EventHubReader(iotHubConnectionString, eventHubConsumerGroup);
(async () => {
await eventHubReader.startReadMessage((message, date, deviceId) => {
try {
const payload = {
IotData: message,
MessageDate: date || Date.now().toISOString(),
DeviceId: deviceId,
};
wss.broadcast(JSON.stringify(payload));
} catch (err) {
console.error('Error broadcasting: [%s] from [%s].', err, message);
}
});
})().catch();

home.js

// how to connect with backend to use the websocket?

I am not entirely sure how this websocket for this route is supposed to work in relation to the rest of my routes and pages. Does it affect how the server is supposed to work for the rest of the pages? How am I supposed to incorporate this?

答案1

得分: 0

以下是您要翻译的内容:

  • 将WebSocket服务器和Azure IoT Hub通信集成到您的现有server.js文件中。

  • 尝试将WebSocket服务器和Express服务器合并为一个处理WebSocket和REST API请求的HTTP服务器。请查看下面的示例,其中我创建了WebSocket服务器和HTTP服务器。

// 不正确:分开创建WebSocket服务器和HTTP服务器
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
server.listen(PORT, () => {
  console.log('Listening on %d.', server.address().port);
});

// 正确:合并创建WebSocket服务器和HTTP服务器
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// 继续WebSocket和IoT Hub的逻辑...
  • 建立WebSocket连接,以从您的IoT Hub接收实时数据。您需要使用React Native(Expo)提供的WebSocket API。

home.js:

import React, { useEffect } from 'react';
import { View, Text } from 'react-native';

const HomeScreen = () => {
  // 用于保存从WebSocket接收到的实时数据的状态
  const [realTimeData, setRealTimeData] = React.useState('');

  useEffect(() => {
    // WebSocket连接URL
    const webSocketURL = 'ws://YOUR_SERVER_IP_ADDRESS:3000';

    // 创建一个新的WebSocket实例
    const socket = new WebSocket(webSocketURL);

    // WebSocket连接建立时的事件处理程序
    socket.onopen = () => {
      console.log('WebSocket connected.');
    };

    // WebSocket接收到消息时的事件处理程序
    socket.onmessage = (event) => {
      // 解析接收到的消息
      const data = JSON.parse(event.data);
      // 使用接收到的数据更新状态
      setRealTimeData(data.IotData);
    };

    // WebSocket错误时的事件处理程序
    socket.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    // 当组件卸载时清理WebSocket连接
    return () => {
      socket.close();
    };
  }, []); // 空的依赖数组,以便仅运行效果一次

  return (
    <View>
      <Text>实时IoT数据:</Text>
      <Text>{realTimeData}</Text>
      {/* 为您的主屏幕添加其他组件和UI元素 */}
    </View>
  );
};

export default HomeScreen;
  • WebSocket URL 应设置为服务器的地址和端口,即您的后端正在运行的位置。

  • WebSocket将正常工作,前端将通过WebSocket连接实时从IoT Hub接收数据,同时仍然使用REST API处理其他页面和功能。

有关更多详细信息,请参考此WebSocket连接与React-RouterSO

英文:

>Does it affect how the server is supposed to work for the rest of the pages? How am I supposed to incorporate this?

  • Integrate the WebSocket server and the Azure IoT Hub communication into your existing server.js file.

  • Try to combine WebSocket server and the Express server into one HTTP server that handles both WebSocket and REST API requests. Check below which I create the WebSocket server and the HTTP server.

// Incorrect: Separate creation of WebSocket server and HTTP server
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
server.listen(PORT, () =&gt; {
  console.log(&#39;Listening on %d.&#39;, server.address().port);
});

// Correct: Combined creation of WebSocket server and HTTP server
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// Continue with the WebSocket and IoT Hub logic...
  • Establish WebSocket connection to receive real-time data from your IoT Hub. You will need to use the WebSocket API provided by React Native (Expo).

home.js:

import React, { useEffect } from &#39;react&#39;;
import { View, Text } from &#39;react-native&#39;;

const HomeScreen = () =&gt; {
  // State to hold the real-time data received from the WebSocket
  const [realTimeData, setRealTimeData] = React.useState(&#39;&#39;);

  useEffect(() =&gt; {
    // WebSocket connection URL
    const webSocketURL = &#39;ws://YOUR_SERVER_IP_ADDRESS:3000&#39;;

    // Create a new WebSocket instance
    const socket = new WebSocket(webSocketURL);

    // Event handler for when the WebSocket connection is established
    socket.onopen = () =&gt; {
      console.log(&#39;WebSocket connected.&#39;);
    };

    // Event handler for when the WebSocket receives a message
    socket.onmessage = (event) =&gt; {
      // Parse the received message
      const data = JSON.parse(event.data);
      // Update the state with the received data
      setRealTimeData(data.IotData);
    };

    // Event handler for WebSocket errors
    socket.onerror = (error) =&gt; {
      console.error(&#39;WebSocket error:&#39;, error);
    };

    // Clean up the WebSocket connection when the component is unmounted
    return () =&gt; {
      socket.close();
    };
  }, []); // Empty dependency array to run the effect only once

  return (
    &lt;View&gt;
      &lt;Text&gt;Real-time IoT Data:&lt;/Text&gt;
      &lt;Text&gt;{realTimeData}&lt;/Text&gt;
      {/* Add other components and UI elements for your Home screen */}
    &lt;/View&gt;
  );
};

export default HomeScreen;
  • The WebSocket URL should be set to the server's address and port, where your backend is running.

  • WebSocket will work, and the frontend will receive real-time data from the IoT Hub through the WebSocket connection while still using the REST API for other pages and functionality.

For more details refer this WebSocket connection with React-Router and SO

huangapple
  • 本文由 发表于 2023年7月28日 04:01:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76783049.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定