modbus rtu 1.2

This commit is contained in:
flower_linux
2026-04-14 15:39:04 +08:00
parent b44f5e275e
commit 4f2ada08c8
31 changed files with 749 additions and 300 deletions

View File

@@ -0,0 +1,43 @@
#include "api/http/RestApiCommandHttp.h"
#include <QHttpServerResponse>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include "api/ipc_dbus/CommandDispatcher.h"
namespace softbus::api::http
{
QHttpServerResponse makeCommandHttpResponse(const QString& id, const softbus::api::ipc::DispatchResult& r)
{
QJsonObject o;
o.insert(QStringLiteral("id"), id);
o.insert(QStringLiteral("code"), r.code);
o.insert(QStringLiteral("message"), r.message);
o.insert(QStringLiteral("data"), r.data);
const auto httpStatus =
r.code == 0 ? QHttpServerResponse::StatusCode::Ok : QHttpServerResponse::StatusCode::BadRequest;
return QHttpServerResponse(o, httpStatus);
}
QHttpServerResponse handleExecuteCommandHttpBody(const QByteArray& body, softbus::api::ipc::CommandDispatcher& dispatcher)
{
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(body, &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
return makeCommandHttpResponse(QString(), {4000, QStringLiteral("invalid_json"), {}});
}
const QJsonObject req = doc.object();
const QString id = req.value(QStringLiteral("id")).toString();
const QString action = req.value(QStringLiteral("action")).toString();
const QJsonObject params = req.value(QStringLiteral("params")).toObject();
if (action.isEmpty()) {
return makeCommandHttpResponse(id, {4003, QStringLiteral("invalid_action"), {}});
}
const auto result = dispatcher.dispatch(action, params);
return makeCommandHttpResponse(id, result);
}
} // namespace softbus::api::http

View File

@@ -0,0 +1,22 @@
#pragma once
#include <QByteArray>
#include <QHttpServerResponse>
#include <QString>
namespace softbus::api::ipc
{
struct DispatchResult;
class CommandDispatcher;
} // namespace softbus::api::ipc
namespace softbus::api::http
{
/// Same envelope as D-Bus `executeCommand` response body (id/code/message/data).
QHttpServerResponse makeCommandHttpResponse(const QString& id, const softbus::api::ipc::DispatchResult& r);
/// POST body same shape as D-Bus request: { "id", "action", "params" }.
QHttpServerResponse handleExecuteCommandHttpBody(const QByteArray& body, softbus::api::ipc::CommandDispatcher& dispatcher);
} // namespace softbus::api::http

View File

@@ -0,0 +1,67 @@
#include "api/http/RestApiHttpConfig.h"
#include <QFile>
#include <QIODevice>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
namespace softbus::api::http
{
bool loadRestApiHttpListenOptions(const QString& configPath, RestApiHttpListenOptions* out, QString* err)
{
if (!out) {
if (err) {
*err = QStringLiteral("null_output");
}
return false;
}
*out = {};
if (configPath.isEmpty()) {
if (err) {
*err = QStringLiteral("config_path_empty");
}
return false;
}
QFile f(configPath);
if (!f.exists()) {
return true;
}
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
if (err) {
*err = QStringLiteral("config_open_failed");
}
return false;
}
const QByteArray raw = f.readAll();
f.close();
QJsonParseError pe;
const QJsonDocument doc = QJsonDocument::fromJson(raw, &pe);
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
if (err) {
*err = QStringLiteral("config_json_invalid");
}
return false;
}
const QJsonObject root = doc.object();
const QJsonObject api = root.value(QStringLiteral("api")).toObject();
const QJsonObject httpCfg = api.value(QStringLiteral("http")).toObject();
if (httpCfg.isEmpty()) {
return true;
}
out->sectionPresent = true;
out->enabled = httpCfg.value(QStringLiteral("enabled")).toBool(false);
out->bindHost = httpCfg.value(QStringLiteral("bind")).toString(QStringLiteral("127.0.0.1"));
if (out->bindHost.isEmpty()) {
out->bindHost = QStringLiteral("127.0.0.1");
}
out->port = httpCfg.value(QStringLiteral("port")).toInt(18080);
return true;
}
} // namespace softbus::api::http

View File

@@ -0,0 +1,21 @@
#pragma once
#include <QString>
namespace softbus::api::http
{
/// Parsed `api.http` block from daemon_config.json.
struct RestApiHttpListenOptions
{
bool sectionPresent{false};
bool enabled{false};
QString bindHost;
int port{18080};
};
/// Reads and validates `api.http`. On success returns true; on missing file returns true with sectionPresent=false.
/// Hard errors (open/parse) set err and return false.
bool loadRestApiHttpListenOptions(const QString& configPath, RestApiHttpListenOptions* out, QString* err = nullptr);
} // namespace softbus::api::http

View File

@@ -0,0 +1,44 @@
#include "api/http/RestApiRoutes.h"
#include <QHttpServer>
#include <QHttpServerRequest>
#include <QHttpServerResponse>
#include <QHttpServerResponder>
#include <QJsonObject>
#include <utility>
#include "api/http/RestApiCommandHttp.h"
#include "api/ipc_dbus/CommandDispatcher.h"
namespace softbus::api::http
{
void registerSoftbusRestApiRoutes(QHttpServer* server,
QObject* routeContext,
softbus::api::ipc::CommandDispatcher* dispatcher)
{
if (!server || !routeContext || !dispatcher) {
return;
}
server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Get, routeContext, []() {
return QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true}}, QHttpServerResponse::StatusCode::Ok);
});
server->route(QStringLiteral("/v1/status"), QHttpServerRequest::Method::Get, routeContext, [dispatcher]() {
return makeCommandHttpResponse(QString(), dispatcher->dispatch(QStringLiteral("get_status"), {}));
});
server->route(QStringLiteral("/v1/command"), QHttpServerRequest::Method::Post, routeContext,
[dispatcher](const QHttpServerRequest& request) {
return handleExecuteCommandHttpBody(request.body(), *dispatcher);
});
server->setMissingHandler(routeContext, [](const QHttpServerRequest&, QHttpServerResponder& responder) {
QHttpServerResponse resp(QJsonObject{{QStringLiteral("error"), QStringLiteral("not_found")}},
QHttpServerResponse::StatusCode::NotFound);
responder.sendResponse(std::move(resp));
});
}
} // namespace softbus::api::http

View File

@@ -0,0 +1,19 @@
#pragma once
class QObject;
class QHttpServer;
namespace softbus::api::ipc
{
class CommandDispatcher;
} // namespace softbus::api::ipc
namespace softbus::api::http
{
/// Registers REST routes on `server`. `routeContext` is the QObject passed to QHttpServer::route (lifetime).
void registerSoftbusRestApiRoutes(QHttpServer* server,
QObject* routeContext,
softbus::api::ipc::CommandDispatcher* dispatcher);
} // namespace softbus::api::http

View File

@@ -0,0 +1,115 @@
#include "api/http/RestApiServer.h"
#include <QHostAddress>
#include <QHttpServer>
#include <QTcpServer>
#include "api/http/RestApiHttpConfig.h"
#include "api/http/RestApiRoutes.h"
#include "api/ipc_dbus/CommandDispatcher.h"
#include "utils/logs/logging.h"
namespace softbus::api::http
{
struct RestApiServer::Impl
{
std::unique_ptr<softbus::api::ipc::CommandDispatcher> dispatcher;
std::unique_ptr<QHttpServer> http;
std::unique_ptr<QTcpServer> tcp;
};
RestApiServer::RestApiServer(QObject* parent)
: QObject(parent)
, m_impl(std::make_unique<Impl>())
{
m_impl->dispatcher = std::make_unique<softbus::api::ipc::CommandDispatcher>();
}
RestApiServer::~RestApiServer()
{
stop();
}
void RestApiServer::stop()
{
if (!m_impl || !m_impl->http) {
return;
}
m_impl->http->disconnect();
m_impl->http.reset();
m_impl->tcp.reset();
}
bool RestApiServer::tryStartFromConfigFile(const QString& configPath, QString* err)
{
if (!m_impl || !m_impl->dispatcher) {
if (err) {
*err = QStringLiteral("internal_error");
}
return false;
}
RestApiHttpListenOptions opts;
if (!loadRestApiHttpListenOptions(configPath, &opts, err)) {
return false;
}
if (!opts.sectionPresent) {
LOG_INFO() << "RestApiServer: no api.http section, HTTP disabled";
return true;
}
if (!opts.enabled) {
LOG_INFO() << "RestApiServer: api.http.enabled is false or missing, HTTP disabled";
return true;
}
if (opts.port <= 0 || opts.port > 65535) {
if (err) {
*err = QStringLiteral("invalid_port");
}
return false;
}
stop();
m_impl->http = std::make_unique<QHttpServer>(this);
m_impl->tcp = std::make_unique<QTcpServer>();
QHttpServer* srv = m_impl->http.get();
registerSoftbusRestApiRoutes(srv, this, m_impl->dispatcher.get());
const QHostAddress addr(opts.bindHost);
if (addr.isNull()) {
if (err) {
*err = QStringLiteral("invalid_bind_address");
}
stop();
return false;
}
if (!m_impl->tcp->listen(addr, static_cast<quint16>(opts.port))) {
if (err) {
*err = QStringLiteral("tcp_listen_failed");
}
LOG_ERROR() << "RestApiServer: listen failed bind=" << opts.bindHost << "port=" << opts.port
<< "err=" << m_impl->tcp->errorString();
stop();
return false;
}
if (!srv->bind(m_impl->tcp.get())) {
if (err) {
*err = QStringLiteral("http_bind_failed");
}
LOG_ERROR() << "RestApiServer: QHttpServer::bind failed";
stop();
return false;
}
(void)m_impl->tcp.release();
LOG_INFO() << "RestApiServer: listening http://" << opts.bindHost << ":" << opts.port;
return true;
}
} // namespace softbus::api::http

View File

@@ -0,0 +1,29 @@
#pragma once
#include <QObject>
#include <QString>
#include <memory>
namespace softbus::api::http
{
class RestApiServer final : public QObject
{
Q_OBJECT
public:
explicit RestApiServer(QObject* parent = nullptr);
~RestApiServer() override;
/// Reads `api.http` from JSON config. If disabled or missing, returns true and does nothing.
/// If enabled but listen/bind fails, returns false and sets err.
bool tryStartFromConfigFile(const QString& configPath, QString* err = nullptr);
void stop();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace softbus::api::http