mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Merge pull request #665 from Palakis/feature/batch-requests
Batch Requests
This commit is contained in:
commit
80d82861ae
@ -46,6 +46,8 @@ const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap{
|
|||||||
{ "TriggerHotkeyByName", &WSRequestHandler::TriggerHotkeyByName },
|
{ "TriggerHotkeyByName", &WSRequestHandler::TriggerHotkeyByName },
|
||||||
{ "TriggerHotkeyBySequence", &WSRequestHandler::TriggerHotkeyBySequence },
|
{ "TriggerHotkeyBySequence", &WSRequestHandler::TriggerHotkeyBySequence },
|
||||||
|
|
||||||
|
{ "ExecuteBatch", &WSRequestHandler::ExecuteBatch },
|
||||||
|
|
||||||
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
|
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
|
||||||
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
|
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
|
||||||
{ "GetSceneList", &WSRequestHandler::GetSceneList },
|
{ "GetSceneList", &WSRequestHandler::GetSceneList },
|
||||||
|
@ -64,6 +64,8 @@ class WSRequestHandler {
|
|||||||
RpcResponse TriggerHotkeyByName(const RpcRequest&);
|
RpcResponse TriggerHotkeyByName(const RpcRequest&);
|
||||||
RpcResponse TriggerHotkeyBySequence(const RpcRequest&);
|
RpcResponse TriggerHotkeyBySequence(const RpcRequest&);
|
||||||
|
|
||||||
|
RpcResponse ExecuteBatch(const RpcRequest&);
|
||||||
|
|
||||||
RpcResponse SetCurrentScene(const RpcRequest&);
|
RpcResponse SetCurrentScene(const RpcRequest&);
|
||||||
RpcResponse GetCurrentScene(const RpcRequest&);
|
RpcResponse GetCurrentScene(const RpcRequest&);
|
||||||
RpcResponse GetSceneList(const RpcRequest&);
|
RpcResponse GetSceneList(const RpcRequest&);
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WSEvents.h"
|
#include "WSEvents.h"
|
||||||
|
#include "protocol/OBSRemoteProtocol.h"
|
||||||
|
|
||||||
#define CASE(x) case x: return #x;
|
#define CASE(x) case x: return #x;
|
||||||
const char *describe_output_format(int format) {
|
const char *describe_output_format(int format) {
|
||||||
@ -415,3 +416,60 @@ RpcResponse WSRequestHandler::TriggerHotkeyBySequence(const RpcRequest& request)
|
|||||||
|
|
||||||
return request.success();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a list of requests sequentially (one-by-one on the same thread).
|
||||||
|
*
|
||||||
|
* @param {Array<Object>} `requests` Array of requests to perform. Executed in order.
|
||||||
|
* @param {String} `requests.*.request-type` Request type. Eg. `GetVersion`.
|
||||||
|
* @param {String (Optional)} `requests.*.message-id` ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified.
|
||||||
|
* @param {boolean (Optional)} `abortOnFail` Stop processing batch requests if one returns a failure.
|
||||||
|
*
|
||||||
|
* @return {Array<Object>} `results` Batch requests results, ordered sequentially.
|
||||||
|
* @return {String} `results.*.message-id` ID of the individual request which was originally provided by the client.
|
||||||
|
* @return {String} `results.*.status` Status response as string. Either `ok` or `error`.
|
||||||
|
* @return {String (Optional)} `results.*.error` Error message accompanying an `error` status.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ExecuteBatch
|
||||||
|
* @category general
|
||||||
|
* @since 4.9.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::ExecuteBatch(const RpcRequest& request) {
|
||||||
|
if (!request.hasField("requests")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool abortOnFail = obs_data_get_bool(request.parameters(), "abortOnFail");
|
||||||
|
|
||||||
|
OBSDataArrayAutoRelease results = obs_data_array_create();
|
||||||
|
|
||||||
|
OBSDataArrayAutoRelease requests = obs_data_get_array(request.parameters(), "requests");
|
||||||
|
size_t requestsCount = obs_data_array_count(requests);
|
||||||
|
for (size_t i = 0; i < requestsCount; i++) {
|
||||||
|
OBSDataAutoRelease requestData = obs_data_array_item(requests, i);
|
||||||
|
QString messageId = obs_data_get_string(requestData, "message-id");
|
||||||
|
QString methodName = obs_data_get_string(requestData, "request-type");
|
||||||
|
obs_data_unset_user_value(requestData, "request-type");
|
||||||
|
obs_data_unset_user_value(requestData, "message-id");
|
||||||
|
|
||||||
|
// build RpcRequest from json data object
|
||||||
|
RpcRequest subRequest(messageId, methodName, requestData);
|
||||||
|
|
||||||
|
// execute the request
|
||||||
|
RpcResponse subResponse = processRequest(subRequest);
|
||||||
|
|
||||||
|
// transform response into json data
|
||||||
|
OBSDataAutoRelease subResponseData = OBSRemoteProtocol::rpcResponseToJsonData(subResponse);
|
||||||
|
|
||||||
|
obs_data_array_push_back(results, subResponseData);
|
||||||
|
|
||||||
|
// if told to abort on fail and a failure occurs, stop request processing and return the progress
|
||||||
|
if (abortOnFail && (subResponse.status() == RpcResponse::Status::Error))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_array(response, "results", results);
|
||||||
|
return request.success(response);
|
||||||
|
}
|
||||||
|
@ -133,8 +133,7 @@ void WSServer::stop()
|
|||||||
|
|
||||||
void WSServer::broadcast(const RpcEvent& event)
|
void WSServer::broadcast(const RpcEvent& event)
|
||||||
{
|
{
|
||||||
OBSRemoteProtocol protocol;
|
std::string message = OBSRemoteProtocol::encodeEvent(event);
|
||||||
std::string message = protocol.encodeEvent(event);
|
|
||||||
|
|
||||||
if (GetConfig()->DebugEnabled) {
|
if (GetConfig()->DebugEnabled) {
|
||||||
blog(LOG_INFO, "Update << '%s'", message.c_str());
|
blog(LOG_INFO, "Update << '%s'", message.c_str());
|
||||||
@ -190,8 +189,7 @@ void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
|
|||||||
}
|
}
|
||||||
|
|
||||||
WSRequestHandler requestHandler(connProperties);
|
WSRequestHandler requestHandler(connProperties);
|
||||||
OBSRemoteProtocol protocol;
|
std::string response = OBSRemoteProtocol::processMessage(requestHandler, payload);
|
||||||
std::string response = protocol.processMessage(requestHandler, payload);
|
|
||||||
|
|
||||||
if (GetConfig()->DebugEnabled) {
|
if (GetConfig()->DebugEnabled) {
|
||||||
blog(LOG_INFO, "Response << '%s'", response.c_str());
|
blog(LOG_INFO, "Response << '%s'", response.c_str());
|
||||||
|
@ -31,11 +31,15 @@ std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler,
|
|||||||
OBSDataAutoRelease data = obs_data_create_from_json(msg);
|
OBSDataAutoRelease data = obs_data_create_from_json(msg);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
||||||
return errorResponse(QString::Null(), "invalid JSON payload");
|
return jsonDataToString(
|
||||||
|
errorResponse(nullptr, "invalid JSON payload")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obs_data_has_user_value(data, "request-type") || !obs_data_has_user_value(data, "message-id")) {
|
if (!obs_data_has_user_value(data, "request-type") || !obs_data_has_user_value(data, "message-id")) {
|
||||||
return errorResponse(QString::Null(), "missing request parameters");
|
return jsonDataToString(
|
||||||
|
errorResponse(nullptr, "missing request parameters")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString methodName = obs_data_get_string(data, "request-type");
|
QString methodName = obs_data_get_string(data, "request-type");
|
||||||
@ -49,15 +53,8 @@ std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler,
|
|||||||
RpcRequest request(messageId, methodName, params);
|
RpcRequest request(messageId, methodName, params);
|
||||||
RpcResponse response = requestHandler.processRequest(request);
|
RpcResponse response = requestHandler.processRequest(request);
|
||||||
|
|
||||||
OBSData additionalFields = response.additionalFields();
|
OBSDataAutoRelease responseData = rpcResponseToJsonData(response);
|
||||||
switch (response.status()) {
|
return jsonDataToString(responseData);
|
||||||
case RpcResponse::Status::Ok:
|
|
||||||
return successResponse(messageId, additionalFields);
|
|
||||||
case RpcResponse::Status::Error:
|
|
||||||
return errorResponse(messageId, response.errorMessage(), additionalFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::string();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event)
|
std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event)
|
||||||
@ -87,33 +84,56 @@ std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event)
|
|||||||
return std::string(obs_data_get_json(eventData));
|
return std::string(obs_data_get_json(eventData));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OBSRemoteProtocol::buildResponse(QString messageId, QString status, obs_data_t* fields)
|
obs_data_t* OBSRemoteProtocol::rpcResponseToJsonData(const RpcResponse& response)
|
||||||
{
|
{
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
QByteArray messageIdBytes = response.messageId().toUtf8();
|
||||||
if (!messageId.isNull()) {
|
const char* messageId = messageIdBytes.constData();
|
||||||
obs_data_set_string(response, "message-id", messageId.toUtf8().constData());
|
|
||||||
}
|
|
||||||
obs_data_set_string(response, "status", status.toUtf8().constData());
|
|
||||||
|
|
||||||
if (fields) {
|
OBSData additionalFields = response.additionalFields();
|
||||||
obs_data_apply(response, fields);
|
switch (response.status()) {
|
||||||
|
case RpcResponse::Status::Ok:
|
||||||
|
return successResponse(messageId, additionalFields);
|
||||||
|
case RpcResponse::Status::Error:
|
||||||
|
return errorResponse(messageId, response.errorMessage().toUtf8().constData(), additionalFields);
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string responseString = obs_data_get_json(response);
|
return nullptr;
|
||||||
return responseString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OBSRemoteProtocol::successResponse(QString messageId, obs_data_t* fields)
|
obs_data_t* OBSRemoteProtocol::successResponse(const char* messageId, obs_data_t* fields)
|
||||||
{
|
{
|
||||||
return buildResponse(messageId, "ok", fields);
|
return buildResponse(messageId, "ok", fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OBSRemoteProtocol::errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields)
|
obs_data_t* OBSRemoteProtocol::errorResponse(const char* messageId, const char* errorMessage, obs_data_t* additionalFields)
|
||||||
{
|
{
|
||||||
OBSDataAutoRelease fields = obs_data_create();
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
if (additionalFields) {
|
if (additionalFields) {
|
||||||
obs_data_apply(fields, additionalFields);
|
obs_data_apply(fields, additionalFields);
|
||||||
}
|
}
|
||||||
obs_data_set_string(fields, "error", errorMessage.toUtf8().constData());
|
obs_data_set_string(fields, "error", errorMessage);
|
||||||
return buildResponse(messageId, "error", fields);
|
return buildResponse(messageId, "error", fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obs_data_t* OBSRemoteProtocol::buildResponse(const char* messageId, const char* status, obs_data_t* fields)
|
||||||
|
{
|
||||||
|
obs_data_t* response = obs_data_create();
|
||||||
|
if (messageId) {
|
||||||
|
obs_data_set_string(response, "message-id", messageId);
|
||||||
|
}
|
||||||
|
obs_data_set_string(response, "status", status);
|
||||||
|
|
||||||
|
if (fields) {
|
||||||
|
obs_data_apply(response, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OBSRemoteProtocol::jsonDataToString(OBSDataAutoRelease data)
|
||||||
|
{
|
||||||
|
std::string responseString = obs_data_get_json(data);
|
||||||
|
return responseString;
|
||||||
|
}
|
||||||
|
@ -20,7 +20,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <obs-data.h>
|
#include <obs-data.h>
|
||||||
#include <QtCore/QString>
|
|
||||||
|
#include "../rpc/RpcResponse.h"
|
||||||
|
|
||||||
class WSRequestHandler;
|
class WSRequestHandler;
|
||||||
class RpcEvent;
|
class RpcEvent;
|
||||||
@ -28,11 +29,13 @@ class RpcEvent;
|
|||||||
class OBSRemoteProtocol
|
class OBSRemoteProtocol
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string processMessage(WSRequestHandler& requestHandler, std::string message);
|
static std::string processMessage(WSRequestHandler& requestHandler, std::string message);
|
||||||
std::string encodeEvent(const RpcEvent& event);
|
static std::string encodeEvent(const RpcEvent& event);
|
||||||
|
static obs_data_t* rpcResponseToJsonData(const RpcResponse& response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string buildResponse(QString messageId, QString status, obs_data_t* fields = nullptr);
|
static obs_data_t* successResponse(const char* messageId, obs_data_t* fields = nullptr);
|
||||||
std::string successResponse(QString messageId, obs_data_t* fields = nullptr);
|
static obs_data_t* errorResponse(const char* messageId, const char* errorMessage, obs_data_t* additionalFields = nullptr);
|
||||||
std::string errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields = nullptr);
|
static obs_data_t* buildResponse(const char* messageId, const char*, obs_data_t* fields = nullptr);
|
||||||
|
static std::string jsonDataToString(OBSDataAutoRelease data);
|
||||||
};
|
};
|
||||||
|
@ -36,7 +36,7 @@ public:
|
|||||||
obs_data_t* additionalFields = nullptr
|
obs_data_t* additionalFields = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
Status status() {
|
const Status status() const {
|
||||||
return _status;
|
return _status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user