context bind

This commit is contained in:
flower_linux
2026-04-22 17:25:51 +08:00
parent 11bc015444
commit 7459cc0f36
31 changed files with 1427 additions and 31 deletions

View File

@@ -78,7 +78,57 @@
| `GET` | `/v1/status` | 透传 `get_status` 的状态响应 |
| `POST` | `/v1/command` | 统一命令入口(请求体为 executeCommand JSON |
### 5.2 Discovery 路由
### 5.2 设备树 / 设备路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/device-tree` | 无 | 返回当前设备树快照 |
| `GET` | `/api/v1/devices` | 无 | 返回当前运行态设备列表 |
| `GET` | `/api/v1/devices/{id}` | path: `id(int)` | 返回指定运行态设备详情 |
| `PATCH` | `/api/v1/device-tree/nodes/{id}` | body: 任意可变节点字段子集 | 部分更新设备树节点;当前至少保护 `id``params.stableKey``params.online``params.lastSeenTs` |
### 5.3 规则管理路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/rules/status` | 无 | 返回动态规则计数状态 |
| `POST` | `/api/v1/rules/filter` | body: `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `condition?` | 新增过滤规则 |
| `DELETE` | `/api/v1/rules/filter/{id}` | path: `id` | 删除过滤规则 |
| `POST` | `/api/v1/rules/transform` | body: `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `scale?`, `offset?`, `unit?` | 新增转换规则 |
| `DELETE` | `/api/v1/rules/transform/{id}` | path: `id` | 删除转换规则 |
- 说明:
- `transform` 规则已经在 `PipelineEngine::applyTransformAndEnrich(...)` 中生效。
- `filter` 路由当前主要提供“注册表管理”能力;动态 filter 规则尚未真正接入 `PipelineEngine::stage3Filter(...)` 的执行链路。
### 5.4 Pipeline 路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/pipeline/status` | 无 | 返回 pipeline 运行状态、session 数量、队列容量、插件摘要与 counters |
| `GET` | `/api/v1/pipeline/counters` | 无 | 返回 `in/out/drop/error/mergeDrop` 计数器快照 |
| `GET` | `/api/v1/pipeline/config` | 无 | 返回当前运行时配置快照 |
- 说明:`/v1/status` 仍然是 control-plane 摘要(规则 + discovery + mapping不等同于 `/api/v1/pipeline/status`
### 5.5 插件 / 协议能力路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/plugins` | 无 | 返回 parser / framer / mapper 三类逻辑插件的统一列表 |
| `GET` | `/api/v1/plugins/protocols` | 无 | 返回按协议聚合后的能力快照parser/framer/mapper pluginId |
| `GET` | `/api/v1/plugins/mappers` | 无 | 返回 mapper 插件列表 |
### 5.6 Metadata / Profile 路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/metadata` | 无 | 返回 metadata 字典全量快照 |
| `GET` | `/api/v1/metadata/{metadataId}` | path: `metadataId` | 返回指定 metadata 详情 |
| `GET` | `/api/v1/profiles` | 无 | 返回全部设备 profile 快照 |
| `GET` | `/api/v1/profiles/{deviceId}` | path: `deviceId` | 返回指定 `deviceId` 关联的全部 profile |
### 5.7 Discovery 路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
@@ -88,7 +138,7 @@
| `POST` | `/api/v1/discovery/clear` | 无 | 清空发现池样本 |
| `GET` | `/api/v1/discovery/samples` | query: `offset?`, `limit?`, `endpointHash?`, `protocol?` | 拉取样本(按更新时间倒序,且过滤已映射 key |
### 5.3 Mapping 路由
### 5.8 Mapping 路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|

View File

@@ -1,7 +1,12 @@
#include "api/http/RestApiRoutes.h"
#include <algorithm>
#include <array>
#include <mutex>
#include <QDir>
#include <QFileInfo>
#include <QHttpServer>
#include <QHttpHeaders>
#include <QHttpServerRequest>
#include <QHttpServerResponse>
#include <QHttpServerResponder>
@@ -9,13 +14,20 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QUrlQuery>
#include <utility>
#include "api/http/RestApiCommandHttp.h"
#include "api/ipc_dbus/CommandDispatcher.h"
#include "core/metadata/MetadataRegistry.h"
#include "core/metadata/ProfileRegistry.h"
#include "core/plugin_system/PluginManager.h"
#include "device_bus/DeviceBus.h"
#include "message_bus/pipeline/discovery/DiscoveryPool.h"
#include "message_bus/pipeline/mapping/MappingRegistry.h"
#include "message_bus/pipeline/PipelineEngine.h"
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
namespace softbus::api::http
{
@@ -23,6 +35,97 @@ namespace softbus::api::http
namespace
{
QHttpServerResponse withCorsHeaders(QHttpServerResponse response)
{
QHttpHeaders headers = response.headers();
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowOrigin, QByteArrayView("*"));
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowMethods,
QByteArrayView("GET, POST, PATCH, DELETE, OPTIONS"));
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowHeaders,
QByteArrayView("Content-Type, Authorization, X-Requested-With"));
headers.append(QHttpHeaders::WellKnownHeader::AccessControlMaxAge, QByteArrayView("86400"));
response.setHeaders(std::move(headers));
return response;
}
QHttpServerResponse corsPreflightResponse()
{
return withCorsHeaders(QHttpServerResponse(QHttpServerResponse::StatusCode::NoContent));
}
void registerCorsPreflightRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/v1/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/v1/command"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/start"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/stop"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/clear"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/samples"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/mapping/bind"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/device-tree"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/devices"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/devices/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/device-tree/nodes/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/filter"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/filter/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/transform"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/transform/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/counters"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/counters/clear"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/config"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/plugins"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/plugins/protocols"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/plugins/mappers"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/metadata"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/profiles"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
}
constexpr auto kProtectedNodeKeys = std::array{
"id",
"stableKey",
"online",
"lastSeenTs",
};
softbus::core::models::ProtocolType protocolFromString(const QString& s)
{
const QString x = s.trimmed().toUpper();
@@ -47,16 +150,190 @@ softbus::core::models::ProtocolType protocolFromString(const QString& s)
return softbus::core::models::ProtocolType::UNKNOWN;
}
QJsonObject parseJsonBody(const QHttpServerRequest& request)
QJsonObject parseJsonBody(const QHttpServerRequest& request, bool* ok = nullptr)
{
QJsonParseError err;
const auto doc = QJsonDocument::fromJson(request.body(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
const bool parsed = err.error == QJsonParseError::NoError && doc.isObject();
if (ok) {
*ok = parsed;
}
if (!parsed) {
return {};
}
return doc.object();
}
QHttpServerResponse jsonResponse(const QJsonObject& obj,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
{
return withCorsHeaders(QHttpServerResponse(obj, code));
}
QHttpServerResponse okDataResponse(const QJsonValue& data,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
{
return jsonResponse(QJsonObject{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}}, code);
}
QHttpServerResponse errorResponse(const QString& error,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::BadRequest)
{
return jsonResponse(QJsonObject{{QStringLiteral("ok"), false}, {QStringLiteral("error"), error}}, code);
}
QJsonObject deviceTreeNodeToJson(const DeviceTreeNode& node)
{
QJsonObject obj;
obj.insert(QStringLiteral("id"), node.id);
obj.insert(QStringLiteral("name"), node.name);
obj.insert(QStringLiteral("type"), node.type);
obj.insert(QStringLiteral("endpoint"), node.endpoint);
obj.insert(QStringLiteral("driver"), node.driver);
obj.insert(QStringLiteral("capabilitiesRef"), node.capabilitiesRef);
obj.insert(QStringLiteral("params"), node.params);
obj.insert(QStringLiteral("enabled"), node.enabled);
QJsonArray children;
for (const auto& child : node.childrenIds) {
children.push_back(child);
}
obj.insert(QStringLiteral("children"), children);
return obj;
}
QJsonArray devicesToJson(const std::vector<softbus::devices::DeviceInfo>& devices)
{
QJsonArray out;
for (const auto& device : devices) {
out.push_back(device.toJsonObject());
}
return out;
}
QString resolveConfigBaseDir()
{
QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR");
if (!configBase.isEmpty()) {
return configBase.endsWith(QChar('/')) ? configBase : configBase + QChar('/');
}
return QDir::homePath() + QStringLiteral("/softbus/config/");
}
QString resolveConfigFile(const QString& fileName, const QString& fallbackRelative)
{
const QString homePath = resolveConfigBaseDir() + fileName;
return QFileInfo::exists(homePath) ? homePath : fallbackRelative;
}
bool ensureMetadataLoaded(softbus::core::metadata::MetadataRegistry& registry, QString* error)
{
static std::mutex mtx;
static bool attempted = false;
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
if (!attempted) {
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("metadata_dictionary.json"),
QStringLiteral("config/metadata_dictionary.json")));
}
if (!loaded && error) {
*error = registry.lastError();
}
return loaded;
}
softbus::core::metadata::MetadataRegistry& metadataRegistry()
{
static softbus::core::metadata::MetadataRegistry registry;
return registry;
}
bool ensureProfileLoaded(softbus::core::metadata::ProfileRegistry& registry, QString* error)
{
static std::mutex mtx;
static bool attempted = false;
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
if (!attempted) {
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("device_profiles.json"),
QStringLiteral("config/device_profiles.json")));
}
if (!loaded && error) {
*error = registry.lastError();
}
return loaded;
}
softbus::core::metadata::ProfileRegistry& profileRegistry()
{
static softbus::core::metadata::ProfileRegistry registry;
return registry;
}
bool isProtectedNodeKey(const QString& key)
{
for (const auto* value : kProtectedNodeKeys) {
if (key == QLatin1String(value)) {
return true;
}
}
return false;
}
bool applyNodePatch(DeviceTreeNode& node, const QJsonObject& patch)
{
bool changed = false;
if (patch.contains(QStringLiteral("name")) && patch.value(QStringLiteral("name")).isString()) {
node.name = patch.value(QStringLiteral("name")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("type")) && patch.value(QStringLiteral("type")).isString()) {
node.type = patch.value(QStringLiteral("type")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("endpoint")) && patch.value(QStringLiteral("endpoint")).isString()) {
node.endpoint = patch.value(QStringLiteral("endpoint")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("driver")) && patch.value(QStringLiteral("driver")).isString()) {
node.driver = patch.value(QStringLiteral("driver")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("capabilitiesRef")) && patch.value(QStringLiteral("capabilitiesRef")).isString()) {
node.capabilitiesRef = patch.value(QStringLiteral("capabilitiesRef")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("enabled")) && patch.value(QStringLiteral("enabled")).isBool()) {
node.enabled = patch.value(QStringLiteral("enabled")).toBool();
changed = true;
}
if (patch.contains(QStringLiteral("children")) && patch.value(QStringLiteral("children")).isArray()) {
QVector<QString> children;
for (const auto& child : patch.value(QStringLiteral("children")).toArray()) {
if (child.isString()) {
children.push_back(child.toString());
}
}
node.childrenIds = children;
changed = true;
}
if (patch.contains(QStringLiteral("params")) && patch.value(QStringLiteral("params")).isObject()) {
const auto paramsPatch = patch.value(QStringLiteral("params")).toObject();
for (auto it = paramsPatch.begin(); it != paramsPatch.end(); ++it) {
if (isProtectedNodeKey(it.key())) {
continue;
}
node.params.insert(it.key(), it.value());
changed = true;
}
}
return changed;
}
void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/discovery/start"), QHttpServerRequest::Method::Post, routeContext,
@@ -65,25 +342,25 @@ void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext)
const auto payload = parseJsonBody(request);
const std::int64_t ttlMs = payload.value(QStringLiteral("ttlMs")).toInteger(0);
pool.startSniffing(ttlMs);
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok);
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
});
server->route(QStringLiteral("/api/v1/discovery/stop"), QHttpServerRequest::Method::Post, routeContext, []() {
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
pool.stopSniffing();
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok);
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
});
server->route(QStringLiteral("/api/v1/discovery/status"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
pool.tick();
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok);
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
});
server->route(QStringLiteral("/api/v1/discovery/clear"), QHttpServerRequest::Method::Post, routeContext, []() {
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
pool.clear();
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok);
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
});
server->route(QStringLiteral("/api/v1/discovery/samples"), QHttpServerRequest::Method::Get, routeContext,
@@ -108,7 +385,7 @@ void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext)
o.insert(QStringLiteral("offset"), static_cast<qint64>(offset));
o.insert(QStringLiteral("limit"), static_cast<qint64>(limit));
o.insert(QStringLiteral("status"), pool.status());
return QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok);
return withCorsHeaders(QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok));
});
}
@@ -126,14 +403,14 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
if (!mappingReg.upsert(rule)) {
return QHttpServerResponse(
return withCorsHeaders(QHttpServerResponse(
QJsonObject{{QStringLiteral("error"), QStringLiteral("invalid_mapping_rule")}},
QHttpServerResponse::StatusCode::BadRequest);
QHttpServerResponse::StatusCode::BadRequest));
}
return QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true},
return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true},
{QStringLiteral("compositeKey"),
QString::number(rule.compositeKey)}},
QHttpServerResponse::StatusCode::Ok);
QHttpServerResponse::StatusCode::Ok));
});
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Post, routeContext,
@@ -143,20 +420,264 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
payload.value(QStringLiteral("compositeKey")).toString().toULongLong();
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
const bool ok = mappingReg.remove(compositeKey);
return QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), ok},
return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), ok},
{QStringLiteral("compositeKey"),
QString::number(compositeKey)}},
ok ? QHttpServerResponse::StatusCode::Ok
: QHttpServerResponse::StatusCode::NotFound);
: QHttpServerResponse::StatusCode::NotFound));
});
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
return QHttpServerResponse(QJsonObject{{QStringLiteral("items"), mappingReg.list()}},
QHttpServerResponse::StatusCode::Ok);
return withCorsHeaders(
QHttpServerResponse(QJsonObject{{QStringLiteral("items"), mappingReg.list()}},
QHttpServerResponse::StatusCode::Ok));
});
}
void registerDeviceRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/device-tree"), QHttpServerRequest::Method::Get, routeContext, []() {
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
if (!model) {
return errorResponse(QStringLiteral("device_tree_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(model->toDeviceTreeJson());
});
server->route(QStringLiteral("/api/v1/devices"), QHttpServerRequest::Method::Get, routeContext, []() {
return okDataResponse(devicesToJson(softbus::device_bus::DeviceBus::instance().listDevices()));
});
server->route(QStringLiteral("/api/v1/devices/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](int id) {
const auto device = softbus::device_bus::DeviceBus::instance().findDeviceById(id);
if (!device.has_value()) {
return errorResponse(QStringLiteral("device_not_found"),
QHttpServerResponse::StatusCode::NotFound);
}
return okDataResponse(device->toJsonObject());
});
server->route(QStringLiteral("/api/v1/device-tree/nodes/<arg>"), QHttpServerRequest::Method::Patch, routeContext,
[](const QString& id, const QHttpServerRequest& request) {
bool bodyOk = false;
const auto patch = parseJsonBody(request, &bodyOk);
if (!bodyOk) {
return errorResponse(QStringLiteral("invalid_json"),
QHttpServerResponse::StatusCode::BadRequest);
}
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
if (!model) {
return errorResponse(QStringLiteral("device_tree_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
const int index = model->findIndexById(id);
if (index < 0) {
return errorResponse(QStringLiteral("device_tree_node_not_found"),
QHttpServerResponse::StatusCode::NotFound);
}
auto& node = model->nodesMutable()[index];
const bool changed = applyNodePatch(node, patch);
if (!changed) {
return errorResponse(QStringLiteral("no_mutable_fields_provided"),
QHttpServerResponse::StatusCode::BadRequest);
}
model->rebuildIndex();
auto& bus = softbus::device_bus::DeviceBus::instance();
bus.markDeviceTreeDirty();
(void)bus.reconcileFromTreeModel();
return okDataResponse(deviceTreeNodeToJson(model->nodes().at(index)));
});
}
void registerRuleRoutes(QHttpServer* server,
QObject* routeContext,
softbus::api::ipc::CommandDispatcher* dispatcher)
{
server->route(QStringLiteral("/api/v1/rules/status"), QHttpServerRequest::Method::Get, routeContext, []() {
return okDataResponse(softbus::message_bus::pipeline::DynamicRuleRegistry::instance().status());
});
server->route(QStringLiteral("/api/v1/rules/filter"), QHttpServerRequest::Method::Post, routeContext,
[dispatcher](const QHttpServerRequest& request) {
bool bodyOk = false;
const auto payload = parseJsonBody(request, &bodyOk);
if (!bodyOk) {
return errorResponse(QStringLiteral("invalid_json"),
QHttpServerResponse::StatusCode::BadRequest);
}
const auto result = dispatcher->dispatch(QStringLiteral("add_filter"), payload);
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
server->route(QStringLiteral("/api/v1/rules/filter/<arg>"), QHttpServerRequest::Method::Delete, routeContext,
[dispatcher](const QString& id) {
const auto result = dispatcher->dispatch(QStringLiteral("remove_filter"),
QJsonObject{{QStringLiteral("id"), id}});
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
server->route(QStringLiteral("/api/v1/rules/transform"), QHttpServerRequest::Method::Post, routeContext,
[dispatcher](const QHttpServerRequest& request) {
bool bodyOk = false;
const auto payload = parseJsonBody(request, &bodyOk);
if (!bodyOk) {
return errorResponse(QStringLiteral("invalid_json"),
QHttpServerResponse::StatusCode::BadRequest);
}
const auto result = dispatcher->dispatch(QStringLiteral("add_transform_rule"), payload);
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
server->route(QStringLiteral("/api/v1/rules/transform/<arg>"), QHttpServerRequest::Method::Delete, routeContext,
[dispatcher](const QString& id) {
const auto result = dispatcher->dispatch(QStringLiteral("remove_transform_rule"),
QJsonObject{{QStringLiteral("id"), id}});
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
}
void registerPipelineRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/pipeline/status"), QHttpServerRequest::Method::Get, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pipeline->status());
});
server->route(QStringLiteral("/api/v1/pipeline/counters"), QHttpServerRequest::Method::Get, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pipeline->countersJson());
});
server->route(QStringLiteral("/api/v1/pipeline/counters/clear"), QHttpServerRequest::Method::Post, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
pipeline->resetCounters();
return okDataResponse(pipeline->countersJson());
});
server->route(QStringLiteral("/api/v1/pipeline/config"), QHttpServerRequest::Method::Get, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pipeline->runtimeConfigJson());
});
}
void registerPluginRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/plugins"), QHttpServerRequest::Method::Get, routeContext, []() {
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
if (!pluginManager) {
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
QJsonArray items;
for (const auto& entry : pluginManager->protocolPluginsSnapshot()) {
items.push_back(entry);
}
for (const auto& entry : pluginManager->protocolFramerPluginsSnapshot()) {
items.push_back(entry);
}
for (const auto& entry : pluginManager->protocolMapperPluginsSnapshot()) {
items.push_back(entry);
}
return okDataResponse(items);
});
server->route(QStringLiteral("/api/v1/plugins/protocols"), QHttpServerRequest::Method::Get, routeContext, []() {
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
if (!pluginManager) {
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pluginManager->protocolCapabilitiesSnapshot());
});
server->route(QStringLiteral("/api/v1/plugins/mappers"), QHttpServerRequest::Method::Get, routeContext, []() {
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
if (!pluginManager) {
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pluginManager->protocolMapperPluginsSnapshot());
});
}
void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/metadata"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& registry = metadataRegistry();
QString error;
if (!ensureMetadataLoaded(registry, &error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
});
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](const QString& metadataId) {
auto& registry = metadataRegistry();
QString error;
if (!ensureMetadataLoaded(registry, &error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
const auto items = registry.list();
for (const auto& item : items) {
const auto obj = item.toObject();
if (obj.value(QStringLiteral("metadataId")).toString() == metadataId) {
return okDataResponse(obj);
}
}
return errorResponse(QStringLiteral("metadata_not_found"),
QHttpServerResponse::StatusCode::NotFound);
});
}
void registerProfileRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/profiles"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& registry = profileRegistry();
QString error;
if (!ensureProfileLoaded(registry, &error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
});
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](const QString& deviceId) {
auto& registry = profileRegistry();
QString error;
if (!ensureProfileLoaded(registry, &error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.listByDeviceId(deviceId));
});
}
} // namespace
void registerSoftbusRestApiRoutes(QHttpServer* server,
@@ -168,24 +689,33 @@ void registerSoftbusRestApiRoutes(QHttpServer* server,
}
server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Get, routeContext, []() {
return QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true}}, QHttpServerResponse::StatusCode::Ok);
return withCorsHeaders(
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"), {}));
return withCorsHeaders(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);
return withCorsHeaders(handleExecuteCommandHttpBody(request.body(), *dispatcher));
});
registerCorsPreflightRoutes(server, routeContext);
registerDiscoveryRoutes(server, routeContext);
registerMappingRoutes(server, routeContext);
registerDeviceRoutes(server, routeContext);
registerRuleRoutes(server, routeContext, dispatcher);
registerPipelineRoutes(server, routeContext);
registerPluginRoutes(server, routeContext);
registerMetadataRoutes(server, routeContext);
registerProfileRoutes(server, routeContext);
server->setMissingHandler(routeContext, [](const QHttpServerRequest&, QHttpServerResponder& responder) {
QHttpServerResponse resp(QJsonObject{{QStringLiteral("error"), QStringLiteral("not_found")}},
QHttpServerResponse::StatusCode::NotFound);
responder.sendResponse(std::move(resp));
responder.sendResponse(withCorsHeaders(std::move(resp)));
});
}

View File

@@ -1,8 +1,17 @@
#include "api/ipc_dbus/CommandDispatcher.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QSaveFile>
#include "device_bus/DeviceBus.h"
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
#include "message_bus/pipeline/discovery/DiscoveryPool.h"
#include "message_bus/pipeline/mapping/MappingRegistry.h"
#include "utils/logs/logging.h"
namespace softbus::api::ipc
{
@@ -10,6 +19,16 @@ namespace softbus::api::ipc
namespace
{
QString rulesConfigPath()
{
const QString daemonConfigPath = softbus::device_bus::DeviceBus::instance().configPath();
if (!daemonConfigPath.isEmpty()) {
const QFileInfo fi(daemonConfigPath);
return fi.dir().filePath(QStringLiteral("runtime_rules.json"));
}
return QDir::homePath() + QStringLiteral("/softbus/config/runtime_rules.json");
}
softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params)
{
softbus::message_bus::pipeline::RuleScope scope;
@@ -19,6 +38,89 @@ softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params)
return scope;
}
QJsonObject scopeToJson(const softbus::message_bus::pipeline::RuleScope& scope)
{
QJsonObject out;
if (scope.endpointHash != 0) {
out.insert(QStringLiteral("endpointHash"), static_cast<qint64>(scope.endpointHash));
}
if (scope.deviceId >= 0) {
out.insert(QStringLiteral("deviceId"), scope.deviceId);
}
if (!scope.stableKey.isEmpty()) {
out.insert(QStringLiteral("stableKey"), scope.stableKey);
}
return out;
}
QJsonObject filterRuleToJson(const softbus::message_bus::pipeline::FilterRule& rule)
{
QJsonObject out;
out.insert(QStringLiteral("id"), rule.id);
out.insert(QStringLiteral("scope"), scopeToJson(rule.scope));
out.insert(QStringLiteral("condition"), rule.condition);
return out;
}
QJsonObject transformRuleToJson(const softbus::message_bus::pipeline::TransformRule& rule)
{
QJsonObject out;
out.insert(QStringLiteral("id"), rule.id);
out.insert(QStringLiteral("scope"), scopeToJson(rule.scope));
out.insert(QStringLiteral("scale"), rule.scale);
out.insert(QStringLiteral("offset"), rule.offset);
if (!rule.unit.isEmpty()) {
out.insert(QStringLiteral("unit"), rule.unit);
}
return out;
}
bool persistRulesToDedicatedConfig(const softbus::message_bus::pipeline::DynamicRuleRegistry& reg)
{
const QString configPath = rulesConfigPath();
if (configPath.isEmpty()) {
LOG_ERROR() << "CommandDispatcher: rules config path is empty, cannot persist rules";
return false;
}
const QFileInfo fi(configPath);
QJsonArray filtersJson;
for (const auto& rule : reg.snapshotAllFilterRules()) {
filtersJson.push_back(filterRuleToJson(rule));
}
QJsonArray transformsJson;
for (const auto& rule : reg.snapshotAllTransformRules()) {
transformsJson.push_back(transformRuleToJson(rule));
}
QJsonObject root;
root.insert(QStringLiteral("filter"), filtersJson);
root.insert(QStringLiteral("transform"), transformsJson);
if (!QDir().mkpath(fi.dir().absolutePath())) {
LOG_ERROR() << "CommandDispatcher: failed to create rules config directory:" << fi.dir().absolutePath();
return false;
}
const QByteArray bytes = QJsonDocument(root).toJson(QJsonDocument::Indented);
QSaveFile out(configPath);
if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG_ERROR() << "CommandDispatcher: failed to open rules config for write:" << configPath;
return false;
}
if (out.write(bytes) != bytes.size()) {
LOG_ERROR() << "CommandDispatcher: failed to write rules config:" << configPath;
out.cancelWriting();
return false;
}
if (!out.commit()) {
LOG_ERROR() << "CommandDispatcher: failed to commit rules config:" << configPath;
return false;
}
LOG_INFO() << "CommandDispatcher: rules persisted to" << configPath;
return true;
}
} // namespace
DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObject& params) const
@@ -34,11 +136,17 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
if (!reg.addFilterRule(rule)) {
return {4001, QStringLiteral("invalid_filter_rule"), {}};
}
if (!persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}};
}
if (action == QStringLiteral("remove_filter")) {
const QString id = params.value(QStringLiteral("id")).toString();
const bool ok = reg.removeFilterRule(id);
if (ok && !persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {ok ? 0 : 4041, ok ? QStringLiteral("ok") : QStringLiteral("filter_not_found"),
QJsonObject{{QStringLiteral("id"), id}}};
}
@@ -52,11 +160,17 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
if (!reg.addTransformRule(rule)) {
return {4002, QStringLiteral("invalid_transform_rule"), {}};
}
if (!persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}};
}
if (action == QStringLiteral("remove_transform_rule")) {
const QString id = params.value(QStringLiteral("id")).toString();
const bool ok = reg.removeTransformRule(id);
if (ok && !persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {ok ? 0 : 4042, ok ? QStringLiteral("ok") : QStringLiteral("transform_rule_not_found"),
QJsonObject{{QStringLiteral("id"), id}}};
}