Add requestResponse handler for incoming calls
Summary: Flipper exposes a call() api to plugins which lets them call their sdk component, and it returns a promise with the response. Currently this is done by sending a fireAndForget request, noting the id of the request, and then receiving fireAndForget requests and matching up the ids to give the result back to the right plugin promise. Instead, it will be simpler to use rsocket requestResponse, instead of fireAndForget, which is for this exact use case. This diff adds a requestResponse handler to the SDK, so that it can deal with such requests and respond accordingly, while preserving the current functionality if it receives a fireAndForget. So this part is backwards compatible and should be safe to land in isolation. A later diff will change the desktop app to use requestResponse, which may not be backwards compatible, so that will have to be deployed more carefully. Reviewed By: passy Differential Revision: D13974049 fbshipit-source-id: b371d94a86b1f186375161ed8f2242a462ce418f
This commit is contained in:
committed by
Facebook Github Bot
parent
8f6138a41c
commit
4a3de26a88
48
xplat/Flipper/FireAndForgetBasedFlipperResponder.h
Normal file
48
xplat/Flipper/FireAndForgetBasedFlipperResponder.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "FlipperResponder.h"
|
||||
#include "FlipperConnectionManager.h"
|
||||
#include <folly/json.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
|
||||
/* Responder for responding to legacy flipper applications.
|
||||
Originally, flipper desktop used fireAndForget for all messages, so calling
|
||||
the SDK would send a fire and forget message, to which the SDK would respond
|
||||
with another one, with an id field that flipper uses to map it to the
|
||||
original request. This Responder should be used when such requests are
|
||||
received.
|
||||
*/
|
||||
class FireAndForgetBasedFlipperResponder : public FlipperResponder {
|
||||
public:
|
||||
FireAndForgetBasedFlipperResponder(
|
||||
FlipperConnectionManager* socket,
|
||||
int64_t responseID)
|
||||
: socket_(socket), responseID_(responseID) {}
|
||||
|
||||
void success(const folly::dynamic& response) const override {
|
||||
const folly::dynamic message =
|
||||
folly::dynamic::object("id", responseID_)("success", response);
|
||||
socket_->sendMessage(message);
|
||||
}
|
||||
|
||||
void error(const folly::dynamic& response) const override {
|
||||
const folly::dynamic message =
|
||||
folly::dynamic::object("id", responseID_)("error", response);
|
||||
socket_->sendMessage(message);
|
||||
}
|
||||
|
||||
private:
|
||||
FlipperConnectionManager* socket_;
|
||||
int64_t responseID_;
|
||||
};
|
||||
|
||||
} // namespace flipper
|
||||
} // namespace facebook
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include "ConnectionContextStore.h"
|
||||
#include "FireAndForgetBasedFlipperResponder.h"
|
||||
#include "FlipperConnectionImpl.h"
|
||||
#include "FlipperConnectionManagerImpl.h"
|
||||
#include "FlipperResponderImpl.h"
|
||||
@@ -154,18 +155,17 @@ void FlipperClient::onDisconnected() {
|
||||
});
|
||||
}
|
||||
|
||||
void FlipperClient::onMessageReceived(const dynamic& message) {
|
||||
performAndReportError([this, &message]() {
|
||||
void FlipperClient::onMessageReceived(
|
||||
const dynamic& message,
|
||||
std::unique_ptr<FlipperResponder> uniqueResponder) {
|
||||
// Convert to shared pointer so we can hold on to it while passing it to the plugin, and still use it
|
||||
// to respond with an error if we catch an exception.
|
||||
std::shared_ptr<FlipperResponder> responder = std::move(uniqueResponder);
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
const auto& method = message["method"];
|
||||
const auto& params = message.getDefault("params");
|
||||
|
||||
std::unique_ptr<FlipperResponderImpl> responder;
|
||||
if (message.find("id") != message.items().end()) {
|
||||
responder.reset(
|
||||
new FlipperResponderImpl(socket_.get(), message["id"].getInt()));
|
||||
}
|
||||
|
||||
if (method == "getPlugins") {
|
||||
dynamic identifiers = dynamic::array();
|
||||
for (const auto& elem : plugins_) {
|
||||
@@ -179,9 +179,12 @@ void FlipperClient::onMessageReceived(const dynamic& message) {
|
||||
if (method == "init") {
|
||||
const auto identifier = params["plugin"].getString();
|
||||
if (plugins_.find(identifier) == plugins_.end()) {
|
||||
throw std::out_of_range(
|
||||
"plugin " + identifier + " not found for method " +
|
||||
method.getString());
|
||||
std::string errorMessage = "Plugin " + identifier +
|
||||
" not found for method " + method.getString();
|
||||
log(errorMessage);
|
||||
responder->error(folly::dynamic::object("message", errorMessage)(
|
||||
"name", "PluginNotFound"));
|
||||
return;
|
||||
}
|
||||
const auto plugin = plugins_.at(identifier);
|
||||
if (!plugin.get()->runInBackground()) {
|
||||
@@ -196,9 +199,12 @@ void FlipperClient::onMessageReceived(const dynamic& message) {
|
||||
if (method == "deinit") {
|
||||
const auto identifier = params["plugin"].getString();
|
||||
if (plugins_.find(identifier) == plugins_.end()) {
|
||||
throw std::out_of_range(
|
||||
"plugin " + identifier + " not found for method " +
|
||||
method.getString());
|
||||
std::string errorMessage = "Plugin " + identifier +
|
||||
" not found for method " + method.getString();
|
||||
log(errorMessage);
|
||||
responder->error(folly::dynamic::object("message", errorMessage)(
|
||||
"name", "PluginNotFound"));
|
||||
return;
|
||||
}
|
||||
const auto plugin = plugins_.at(identifier);
|
||||
if (!plugin.get()->runInBackground()) {
|
||||
@@ -210,23 +216,42 @@ void FlipperClient::onMessageReceived(const dynamic& message) {
|
||||
if (method == "execute") {
|
||||
const auto identifier = params["api"].getString();
|
||||
if (connections_.find(identifier) == connections_.end()) {
|
||||
throw std::out_of_range(
|
||||
"connection " + identifier + " not found for method " +
|
||||
method.getString());
|
||||
std::string errorMessage = "Connection " + identifier +
|
||||
" not found for method " + method.getString();
|
||||
log(errorMessage);
|
||||
responder->error(folly::dynamic::object("message", errorMessage)(
|
||||
"name", "ConnectionNotFound"));
|
||||
return;
|
||||
}
|
||||
const auto& conn = connections_.at(params["api"].getString());
|
||||
conn->call(
|
||||
params["method"].getString(),
|
||||
params.getDefault("params"),
|
||||
std::move(responder));
|
||||
responder);
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic response =
|
||||
dynamic::object("message", "Received unknown method: " + method);
|
||||
responder->error(response);
|
||||
});
|
||||
} catch (std::exception& e) {
|
||||
log(std::string("Error: ") + e.what());
|
||||
if (responder) {
|
||||
responder->error(dynamic::object("message", e.what())(
|
||||
"stacktrace", callstack())("name", e.what()));
|
||||
}
|
||||
} catch (...) {
|
||||
log("Unknown error suppressed in FlipperClient");
|
||||
if (responder) {
|
||||
responder->error(dynamic::object(
|
||||
"message",
|
||||
"Unknown error during " + message["method"] + ". " +
|
||||
folly::toJson(message))("stacktrace", callstack())(
|
||||
"name", "Unknown"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string FlipperClient::callstack() {
|
||||
#if __APPLE__
|
||||
// For some iOS apps, __Unwind_Backtrace symbol wasn't found in sandcastle
|
||||
|
||||
@@ -71,7 +71,9 @@ class FlipperClient : public FlipperConnectionManager::Callbacks {
|
||||
|
||||
void onDisconnected() override;
|
||||
|
||||
void onMessageReceived(const folly::dynamic& message) override;
|
||||
void onMessageReceived(
|
||||
const folly::dynamic& message,
|
||||
std::unique_ptr<FlipperResponder>) override;
|
||||
|
||||
void addPlugin(std::shared_ptr<FlipperPlugin> plugin);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ with corresponding identifiers.
|
||||
class FlipperConnection {
|
||||
public:
|
||||
using FlipperReceiver = std::function<
|
||||
void(const folly::dynamic&, std::unique_ptr<FlipperResponder>)>;
|
||||
void(const folly::dynamic&, std::shared_ptr<FlipperResponder>)>;
|
||||
|
||||
virtual ~FlipperConnection() {}
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FlipperConnection.h"
|
||||
#include "FlipperConnectionManager.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "FlipperConnection.h"
|
||||
#include "FlipperConnectionManager.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
@@ -24,11 +23,14 @@ class FlipperConnectionImpl : public FlipperConnection {
|
||||
void call(
|
||||
const std::string& method,
|
||||
const folly::dynamic& params,
|
||||
std::unique_ptr<FlipperResponder> responder) {
|
||||
std::shared_ptr<FlipperResponder> responder) {
|
||||
if (receivers_.find(method) == receivers_.end()) {
|
||||
throw std::out_of_range("receiver " + method + " not found.");
|
||||
std::string errorMessage = "Receiver " + method + " not found.";
|
||||
log("Error: " + errorMessage);
|
||||
responder->error(folly::dynamic::object("message", errorMessage));
|
||||
return;
|
||||
}
|
||||
receivers_.at(method)(params, std::move(responder));
|
||||
receivers_.at(method)(params, responder);
|
||||
}
|
||||
|
||||
void send(const std::string& method, const folly::dynamic& params) override {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/json.h>
|
||||
#include "FlipperResponder.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
@@ -56,7 +55,9 @@ class FlipperConnectionManager::Callbacks {
|
||||
|
||||
virtual void onDisconnected() = 0;
|
||||
|
||||
virtual void onMessageReceived(const folly::dynamic& message) = 0;
|
||||
virtual void onMessageReceived(
|
||||
const folly::dynamic& message,
|
||||
std::unique_ptr<FlipperResponder>) = 0;
|
||||
};
|
||||
|
||||
} // namespace flipper
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include "ConnectionContextStore.h"
|
||||
#include "FireAndForgetBasedFlipperResponder.h"
|
||||
#include "FlipperResponderImpl.h"
|
||||
#include "FlipperStep.h"
|
||||
#include "Log.h"
|
||||
#include "yarpl/Single.h"
|
||||
|
||||
#define WRONG_THREAD_EXIT_MSG \
|
||||
"ERROR: Aborting flipper initialization because it's not running in the flipper thread."
|
||||
@@ -30,6 +33,8 @@ static constexpr int maxPayloadSize = 0xFFFFFF;
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
|
||||
rsocket::Payload toRSocketPayload(dynamic data);
|
||||
|
||||
class ConnectionEvents : public rsocket::RSocketConnectionEvents {
|
||||
private:
|
||||
FlipperConnectionManagerImpl* websocket_;
|
||||
@@ -72,7 +77,54 @@ class Responder : public rsocket::RSocketResponder {
|
||||
rsocket::Payload request,
|
||||
rsocket::StreamId streamId) {
|
||||
const auto payload = request.moveDataToString();
|
||||
websocket_->callbacks_->onMessageReceived(folly::parseJson(payload));
|
||||
std::unique_ptr<FireAndForgetBasedFlipperResponder> responder;
|
||||
auto message = folly::parseJson(payload);
|
||||
if (message.find("id") != message.items().end()) {
|
||||
auto id = message["id"].getInt();
|
||||
responder =
|
||||
std::make_unique<FireAndForgetBasedFlipperResponder>(websocket_, id);
|
||||
}
|
||||
|
||||
websocket_->callbacks_->onMessageReceived(
|
||||
folly::parseJson(payload), std::move(responder));
|
||||
}
|
||||
|
||||
std::shared_ptr<yarpl::single::Single<rsocket::Payload>>
|
||||
handleRequestResponse(rsocket::Payload request, rsocket::StreamId streamId) {
|
||||
const auto requestString = request.moveDataToString();
|
||||
|
||||
auto dynamicSingle = yarpl::single::Single<folly::dynamic>::create(
|
||||
[payload = std::move(requestString), this](auto observer) {
|
||||
auto responder = std::make_unique<FlipperResponderImpl>(observer);
|
||||
websocket_->callbacks_->onMessageReceived(
|
||||
folly::parseJson(payload), std::move(responder));
|
||||
});
|
||||
|
||||
auto rsocketSingle = yarpl::single::Single<rsocket::Payload>::create(
|
||||
[payload = std::move(requestString), dynamicSingle, this](
|
||||
auto observer) {
|
||||
observer->onSubscribe(
|
||||
yarpl::single::SingleSubscriptions::empty());
|
||||
dynamicSingle->subscribe(
|
||||
[observer, this](folly::dynamic d) {
|
||||
websocket_->connectionEventBase_->runInEventBaseThread(
|
||||
[observer, d]() {
|
||||
try {
|
||||
observer->onSuccess(toRSocketPayload(d));
|
||||
|
||||
} catch (std::exception& e) {
|
||||
log(e.what());
|
||||
observer->onError(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
[observer, this](folly::exception_wrapper e) {
|
||||
websocket_->connectionEventBase_->runInEventBaseThread(
|
||||
[observer, e]() { observer->onError(e); });
|
||||
});
|
||||
});
|
||||
|
||||
return rsocketSingle;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -236,24 +288,18 @@ void FlipperConnectionManagerImpl::setCallbacks(Callbacks* callbacks) {
|
||||
|
||||
void FlipperConnectionManagerImpl::sendMessage(const folly::dynamic& message) {
|
||||
flipperEventBase_->add([this, message]() {
|
||||
std::string json = folly::toJson(message);
|
||||
rsocket::Payload payload = rsocket::Payload(json);
|
||||
auto payloadLength = payload.data->computeChainDataLength();
|
||||
|
||||
DCHECK_LE(payloadLength, maxPayloadSize);
|
||||
if (payloadLength > maxPayloadSize) {
|
||||
auto logMessage =
|
||||
std::string(
|
||||
"Error: Skipping sending message larger than max rsocket payload: ") +
|
||||
json;
|
||||
log(logMessage);
|
||||
try {
|
||||
rsocket::Payload payload = toRSocketPayload(message);
|
||||
if (client_) {
|
||||
client_->getRequester()
|
||||
->fireAndForget(std::move(payload))
|
||||
->subscribe([]() {});
|
||||
}
|
||||
} catch (std::length_error& e) {
|
||||
// Skip sending messages that are too large.
|
||||
log(e.what());
|
||||
return;
|
||||
}
|
||||
if (client_) {
|
||||
client_->getRequester()
|
||||
->fireAndForget(std::move(payload))
|
||||
->subscribe([]() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -341,5 +387,21 @@ bool FlipperConnectionManagerImpl::isRunningInOwnThread() {
|
||||
return flipperEventBase_->isInEventBaseThread();
|
||||
}
|
||||
|
||||
rsocket::Payload toRSocketPayload(dynamic data) {
|
||||
std::string json = folly::toJson(data);
|
||||
rsocket::Payload payload = rsocket::Payload(json);
|
||||
auto payloadLength = payload.data->computeChainDataLength();
|
||||
|
||||
DCHECK_LE(payloadLength, maxPayloadSize);
|
||||
if (payloadLength > maxPayloadSize) {
|
||||
auto logMessage =
|
||||
std::string(
|
||||
"Error: Skipping sending message larger than max rsocket payload: ") +
|
||||
json;
|
||||
throw new std::length_error(logMessage);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
} // namespace flipper
|
||||
} // namespace facebook
|
||||
|
||||
@@ -1,40 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2018-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FlipperResponder.h"
|
||||
#include "FlipperConnectionManager.h"
|
||||
#include <folly/io/async/EventBase.h>
|
||||
#include <folly/json.h>
|
||||
#include <rsocket/RSocketResponder.h>
|
||||
#include "FlipperConnectionManager.h"
|
||||
#include "FlipperResponder.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
|
||||
/* Responder to encapsulate yarpl observables and hide them from flipper core +
|
||||
* plugins */
|
||||
class FlipperResponderImpl : public FlipperResponder {
|
||||
public:
|
||||
FlipperResponderImpl(FlipperConnectionManager* socket, int64_t responseID)
|
||||
: socket_(socket), responseID_(responseID) {}
|
||||
FlipperResponderImpl(
|
||||
std::shared_ptr<yarpl::single::SingleObserver<folly::dynamic>>
|
||||
downstreamObserver)
|
||||
: downstreamObserver_(downstreamObserver) {}
|
||||
|
||||
void success(const folly::dynamic& response) const override {
|
||||
const folly::dynamic message =
|
||||
folly::dynamic::object("id", responseID_)("success", response);
|
||||
socket_->sendMessage(message);
|
||||
const folly::dynamic message = folly::dynamic::object("success", response);
|
||||
downstreamObserver_->onSuccess(message);
|
||||
}
|
||||
|
||||
void error(const folly::dynamic& response) const override {
|
||||
const folly::dynamic message =
|
||||
folly::dynamic::object("id", responseID_)("error", response);
|
||||
socket_->sendMessage(message);
|
||||
const folly::dynamic message = folly::dynamic::object("error", response);
|
||||
downstreamObserver_->onSuccess(message);
|
||||
}
|
||||
|
||||
private:
|
||||
FlipperConnectionManager* socket_;
|
||||
int64_t responseID_;
|
||||
std::shared_ptr<yarpl::single::SingleObserver<folly::dynamic>>
|
||||
downstreamObserver_;
|
||||
};
|
||||
|
||||
} // namespace flipper
|
||||
|
||||
Reference in New Issue
Block a user