Merge pull request #665 from Palakis/feature/batch-requests

Batch Requests
This commit is contained in:
tt2468 2021-02-04 07:50:22 -08:00 committed by GitHub
commit 80d82861ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 119 additions and 36 deletions

View File

@ -46,6 +46,8 @@ const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap{
{ "TriggerHotkeyByName", &WSRequestHandler::TriggerHotkeyByName },
{ "TriggerHotkeyBySequence", &WSRequestHandler::TriggerHotkeyBySequence },
{ "ExecuteBatch", &WSRequestHandler::ExecuteBatch },
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
{ "GetSceneList", &WSRequestHandler::GetSceneList },
@ -190,7 +192,7 @@ WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
{
}
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request){
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request) {
if (GetConfig()->AuthRequired
&& (!authNotRequired.contains(request.methodName()))
&& (!_connProperties.isAuthenticated()))

View File

@ -64,6 +64,8 @@ class WSRequestHandler {
RpcResponse TriggerHotkeyByName(const RpcRequest&);
RpcResponse TriggerHotkeyBySequence(const RpcRequest&);
RpcResponse ExecuteBatch(const RpcRequest&);
RpcResponse SetCurrentScene(const RpcRequest&);
RpcResponse GetCurrentScene(const RpcRequest&);
RpcResponse GetSceneList(const RpcRequest&);

View File

@ -7,6 +7,7 @@
#include "Config.h"
#include "Utils.h"
#include "WSEvents.h"
#include "protocol/OBSRemoteProtocol.h"
#define CASE(x) case x: return #x;
const char *describe_output_format(int format) {
@ -415,3 +416,60 @@ RpcResponse WSRequestHandler::TriggerHotkeyBySequence(const RpcRequest& request)
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);
}

View File

@ -133,8 +133,7 @@ void WSServer::stop()
void WSServer::broadcast(const RpcEvent& event)
{
OBSRemoteProtocol protocol;
std::string message = protocol.encodeEvent(event);
std::string message = OBSRemoteProtocol::encodeEvent(event);
if (GetConfig()->DebugEnabled) {
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);
OBSRemoteProtocol protocol;
std::string response = protocol.processMessage(requestHandler, payload);
std::string response = OBSRemoteProtocol::processMessage(requestHandler, payload);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Response << '%s'", response.c_str());

View File

@ -31,11 +31,15 @@ std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler,
OBSDataAutoRelease data = obs_data_create_from_json(msg);
if (!data) {
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")) {
return errorResponse(QString::Null(), "missing request parameters");
return jsonDataToString(
errorResponse(nullptr, "missing request parameters")
);
}
QString methodName = obs_data_get_string(data, "request-type");
@ -49,15 +53,8 @@ std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler,
RpcRequest request(messageId, methodName, params);
RpcResponse response = requestHandler.processRequest(request);
OBSData additionalFields = response.additionalFields();
switch (response.status()) {
case RpcResponse::Status::Ok:
return successResponse(messageId, additionalFields);
case RpcResponse::Status::Error:
return errorResponse(messageId, response.errorMessage(), additionalFields);
}
return std::string();
OBSDataAutoRelease responseData = rpcResponseToJsonData(response);
return jsonDataToString(responseData);
}
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));
}
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();
if (!messageId.isNull()) {
obs_data_set_string(response, "message-id", messageId.toUtf8().constData());
}
obs_data_set_string(response, "status", status.toUtf8().constData());
QByteArray messageIdBytes = response.messageId().toUtf8();
const char* messageId = messageIdBytes.constData();
if (fields) {
obs_data_apply(response, fields);
OBSData additionalFields = response.additionalFields();
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 responseString;
return nullptr;
}
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);
}
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();
if (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);
}
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;
}

View File

@ -20,7 +20,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <string>
#include <obs-data.h>
#include <QtCore/QString>
#include "../rpc/RpcResponse.h"
class WSRequestHandler;
class RpcEvent;
@ -28,11 +29,13 @@ class RpcEvent;
class OBSRemoteProtocol
{
public:
std::string processMessage(WSRequestHandler& requestHandler, std::string message);
std::string encodeEvent(const RpcEvent& event);
static std::string processMessage(WSRequestHandler& requestHandler, std::string message);
static std::string encodeEvent(const RpcEvent& event);
static obs_data_t* rpcResponseToJsonData(const RpcResponse& response);
private:
std::string buildResponse(QString messageId, QString status, obs_data_t* fields = nullptr);
std::string successResponse(QString messageId, obs_data_t* fields = nullptr);
std::string errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields = nullptr);
static obs_data_t* successResponse(const char* messageId, obs_data_t* fields = nullptr);
static obs_data_t* errorResponse(const char* messageId, const char* 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);
};

View File

@ -36,7 +36,7 @@ public:
obs_data_t* additionalFields = nullptr
);
Status status() {
const Status status() const {
return _status;
}