wsd: use RequestVettingStation for async loading

This replaces the synchronous loading logic
with the new asynchronous one.

Change-Id: I20fd7903cffbbd7c524d8051295113439ef75d5b
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
pull/8389/head
Ashod Nakashian 2023-02-28 06:34:50 -05:00 committed by Ashod Nakashian
parent 597bb4c1ee
commit 7570dd299c
9 changed files with 30 additions and 277 deletions

View File

@ -28,6 +28,7 @@ add_library(androidapp SHARED
../../../../../wsd/COOLWSD.cpp
../../../../../wsd/ClientRequestDispatcher.cpp
../../../../../wsd/RequestDetails.cpp
../../../../../wsd/RequestVettingStation.cpp
../../../../../wsd/Storage.cpp
../../../../../wsd/TileCache.cpp
../../../../../wsd/coolwsd-fork.cpp)

View File

@ -35,6 +35,7 @@
BE5EB5D22140039100E0826C /* COOLWSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5D12140039100E0826C /* COOLWSD.cpp */; };
BE5EB5D22140039100E0826D /* ClientRequestDispatcher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5D12140039100E0826D /* ClientRequestDispatcher.cpp */; };
BE5EB5D421400DC100E0826C /* DocumentBroker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5D321400DC100E0826C /* DocumentBroker.cpp */; };
BE5EB5E721401E0F00E0826C /* RequestVettingStation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5E621401E0F00E0826C /* RequestVettingStation.cpp */; };
BE5EB5D621401E0F00E0826C /* Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5D521401E0F00E0826C /* Storage.cpp */; };
BE5EB5DA2140363100E0826C /* ios.mm in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5D92140363100E0826C /* ios.mm */; };
BE5EB5DC2140480B00E0826C /* ICU.dat in Resources */ = {isa = PBXBuildFile; fileRef = BE5EB5DB2140480B00E0826C /* ICU.dat */; };
@ -580,6 +581,7 @@
BE5EB5D12140039100E0826C /* COOLWSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = COOLWSD.cpp; sourceTree = "<group>"; };
BE5EB5D12140039100E0826D /* ClientRequestDispatcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ClientRequestDispatcher.cpp; sourceTree = "<group>"; };
BE5EB5D321400DC100E0826C /* DocumentBroker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DocumentBroker.cpp; sourceTree = "<group>"; };
BE5EB5E621401E0F00E0826C /* RequestVettingStation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RequestVettingStation.cpp; sourceTree = "<group>"; };
BE5EB5D521401E0F00E0826C /* Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Storage.cpp; sourceTree = "<group>"; };
BE5EB5D92140363100E0826C /* ios.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios.mm; path = ../../ios/ios.mm; sourceTree = "<group>"; };
BE5EB5DB2140480B00E0826C /* ICU.dat */ = {isa = PBXFileReference; lastKnownFileType = file; name = ICU.dat; path = ../../../ICU.dat; sourceTree = "<group>"; };
@ -2215,6 +2217,7 @@
BE484B71228D8622001EE76C /* DocumentBroker.hpp */,
BE5EB5D12140039100E0826C /* COOLWSD.cpp */,
BE5EB5D12140039100E0826D /* ClientRequestDispatcher.cpp */,
BE5EB5E621401E0F00E0826C /* RequestVettingStation.cpp */,
BE5EB5D521401E0F00E0826C /* Storage.cpp */,
BE5EB5CD213FE2D000E0826C /* TileCache.cpp */,
);
@ -3668,6 +3671,8 @@
BE5EB5C5213FE29900E0826C /* MessageQueue.cpp in Sources */,
BE7228E22417BC9F000ADABD /* StringVector.cpp in Sources */,
BE55E0EB2653FCCB007DDF29 /* ConfigUtil.cpp in Sources */,
BE5EB5E721401E0F00E0826C /* RequestVettingStation.cpp in Sources */,
BE5EB5F821401E0F00E0826C /* Storage.cpp in Sources */,
BE5EB5D621401E0F00E0826C /* Storage.cpp in Sources */,
BEA2835621467FDD00848631 /* Kit.cpp in Sources */,
BE8D77322136762500AC58EA /* DocumentViewController.mm in Sources */,

View File

@ -147,6 +147,7 @@ using Poco::Net::PartHandler;
#include <common/SigUtil.hpp>
#include <RequestVettingStation.hpp>
#include <ServerSocket.hpp>
#if MOBILEAPP

View File

@ -58,26 +58,6 @@ extern std::mutex DocBrokersMutex;
extern void cleanupDocBrokers();
namespace
{
void sendLoadResult(const std::shared_ptr<ClientSession>& clientSession, bool success,
const std::string& errorMsg)
{
const std::string result = success ? "" : "Error while loading document";
const std::string resultstr = success ? "true" : "false";
// Some sane limit, otherwise we get problems transferring this
// to the client with large strings (can be a whole webpage)
// Replace reserved characters
std::string errorMsgFormatted = COOLProtocol::getAbbreviatedMessage(errorMsg);
errorMsgFormatted = Poco::translate(errorMsg, "\"", "'");
clientSession->sendMessage("commandresult: { \"command\": \"load\", \"success\": " + resultstr +
", \"result\": \"" + result + "\", \"errorMsg\": \"" +
errorMsgFormatted + "\"}");
}
} // anonymous namespace
/// Find the DocumentBroker for the given docKey, if one exists.
/// Otherwise, creates and adds a new one to DocBrokers.
/// May return null if terminating or MaxDocuments limit is reached.
@ -1655,119 +1635,10 @@ void ClientRequestDispatcher::handleClientWsUpgrade(const Poco::Net::HTTPRequest
#endif
}
LOG_INF("URL [" << url << "] for WS Request.");
const auto uriPublic = RequestDetails::sanitizeURI(url);
const auto docKey = RequestDetails::getDocKey(uriPublic);
const std::string fileId = Util::getFilenameFromURL(docKey);
Util::mapAnonymized(fileId,
fileId); // Identity mapping, since fileId is already obfuscated
LOG_INF("Starting GET request handler for session [" << _id << "] on url ["
<< COOLWSD::anonymizeUrl(url) << "].");
// Indicate to the client that document broker is searching.
static const std::string status("statusindicator: find");
LOG_TRC("Sending to Client [" << status << "].");
ws->sendMessage(status);
LOG_INF("Sanitized URI [" << COOLWSD::anonymizeUrl(url) << "] to ["
<< COOLWSD::anonymizeUrl(uriPublic.toString())
<< "] and mapped to docKey [" << docKey << "] for session ["
<< _id << "].");
// Check if readonly session is required
bool isReadOnly = false;
for (const auto& param : uriPublic.getQueryParameters())
{
LOG_TRC("Query param: " << param.first << ", value: " << param.second);
if (param.first == "permission" && param.second == "readonly")
{
isReadOnly = true;
}
}
LOG_INF("URL [" << COOLWSD::anonymizeUrl(url) << "] is "
<< (isReadOnly ? "readonly" : "writable"));
// Request a kit process for this doc.
std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(
std::static_pointer_cast<ProtocolHandlerInterface>(ws),
DocumentBroker::ChildType::Interactive, url, docKey, _id, uriPublic, mobileAppDocId);
if (docBroker)
{
std::shared_ptr<ClientSession> clientSession =
docBroker->createNewClientSession(ws, _id, uriPublic, isReadOnly, requestDetails);
if (clientSession)
{
// Transfer the client socket to the DocumentBroker when we get back to the poll:
docBroker->setupTransfer(
disposition,
[docBroker, clientSession, ws](const std::shared_ptr<Socket>& moveSocket)
{
try
{
auto streamSocket = std::static_pointer_cast<StreamSocket>(moveSocket);
// Set WebSocketHandler's socket after its construction for shared_ptr goodness.
streamSocket->setHandler(ws);
LOG_DBG_S('#' << moveSocket->getFD() << " handler is "
<< clientSession->getName());
// Add and load the session.
docBroker->addSession(clientSession);
COOLWSD::checkDiskSpaceAndWarnClients(true);
// Users of development versions get just an info
// when reaching max documents or connections
COOLWSD::checkSessionLimitsAndWarnClients();
sendLoadResult(clientSession, true, "");
}
catch (const UnauthorizedRequestException& exc)
{
LOG_ERR_S("Unauthorized Request while starting session on "
<< docBroker->getDocKey() << " for socket #"
<< moveSocket->getFD()
<< ". Terminating connection. Error: " << exc.what());
const std::string msg = "error: cmd=internal kind=unauthorized";
ws->shutdown(WebSocketHandler::StatusCodes::POLICY_VIOLATION, msg);
moveSocket->ignoreInput();
}
catch (const StorageConnectionException& exc)
{
LOG_ERR_S("Storage error while starting session on "
<< docBroker->getDocKey() << " for socket #"
<< moveSocket->getFD()
<< ". Terminating connection. Error: " << exc.what());
const std::string msg = "error: cmd=storage kind=loadfailed";
ws->shutdown(WebSocketHandler::StatusCodes::POLICY_VIOLATION, msg);
moveSocket->ignoreInput();
}
catch (const std::exception& exc)
{
LOG_ERR_S("Error while starting session on "
<< docBroker->getDocKey() << " for socket #"
<< moveSocket->getFD()
<< ". Terminating connection. Error: " << exc.what());
const std::string msg = "error: cmd=storage kind=loadfailed";
ws->shutdown(WebSocketHandler::StatusCodes::POLICY_VIOLATION, msg);
moveSocket->ignoreInput();
}
});
}
else
{
LOG_WRN("Failed to create Client Session with id [" << _id << "] on docKey ["
<< docKey << "].");
throw std::runtime_error("Cannot create client session for doc " + docKey);
}
}
else
{
throw ServiceUnavailableException("Failed to create DocBroker with docKey [" + docKey +
"].");
}
auto rvs = std::make_shared<RequestVettingStation>(_id, ws, requestDetails, socket,
mobileAppDocId);
_requestVettingStations.emplace(_id, rvs);
rvs->handleRequest(*COOLWSD::getWebServerPoll(), disposition);
}
catch (const std::exception& exc)
{

View File

@ -11,6 +11,7 @@
#pragma once
#include <RequestVettingStation.hpp>
#include <RequestDetails.hpp>
#include <Socket.hpp>
#include <WopiProxy.hpp>
@ -18,8 +19,6 @@
#include <string>
#include <memory>
class WopiProxy;
/// Handles incoming connections and dispatches to the appropriate handler.
class ClientRequestDispatcher final : public SimpleSocketHandler
{
@ -114,6 +113,10 @@ private:
/// WASM document request handler. Used only when WASM is enabled.
std::unique_ptr<WopiProxy> _wopiProxy;
/// External requests are first vetted before allocating DocBroker and Kit process.
/// This is a map of the request URI to the RequestVettingStation for vetting.
std::unordered_map<std::string, std::shared_ptr<RequestVettingStation>> _requestVettingStations;
/// Cache for static files, to avoid reading and processing from disk.
static std::map<std::string, std::string> StaticFileContentCache;
};

View File

@ -853,8 +853,7 @@ bool DocumentBroker::download(const std::shared_ptr<ClientSession>& session,
{
LOG_DBG("CheckFileInfo for docKey [" << _docKey << ']');
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
if (!wopiFileInfo)
wopiFileInfo = wopiStorage->getWOPIFileInfo(session->getAuthorization(), *_lockCtx);
wopiStorage->handleWOPIFileInfo(*wopiFileInfo, *_lockCtx);
checkFileInfoCallDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);

View File

@ -9,6 +9,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "RequestDetails.hpp"
#include <Storage.hpp>
#include "WebSocketHandler.hpp"

View File

@ -640,142 +640,19 @@ http::Request WopiStorage::initHttpRequest(const Poco::URI& uri, const Authoriza
return httpRequest;
}
std::unique_ptr<WopiStorage::WOPIFileInfo>
WopiStorage::getWOPIFileInfoForUri(Poco::URI uriObject, const Authorization& auth,
LockContext& lockCtx, unsigned redirectLimit)
void WopiStorage::handleWOPIFileInfo(const WOPIFileInfo& wopiFileInfo, LockContext& lockCtx)
{
ProfileZone profileZone("WopiStorage::getWOPIFileInfo", { {"url", _fileUrl} });
setFileInfo(wopiFileInfo);
// update the access_token to the one matching to the session
auth.authorizeURI(uriObject);
const std::string uriAnonym = COOLWSD::anonymizeUrl(uriObject.toString());
if (COOLWSD::AnonymizeUserData)
Util::mapAnonymized(Util::getFilenameFromURL(wopiFileInfo.getFilename()),
Util::getFilenameFromURL(getUri().toString()));
LOG_DBG("Getting info for wopi uri [" << uriAnonym << "].");
if (wopiFileInfo.getSupportsLocks())
lockCtx.initSupportsLocks();
std::string wopiResponse;
std::chrono::milliseconds callDurationMs;
try
{
std::shared_ptr<http::Session> httpSession = getHttpSession(uriObject);
http::Request httpRequest = initHttpRequest(uriObject, auth);
const auto startTime = std::chrono::steady_clock::now();
LOG_TRC("WOPI::CheckFileInfo request header for URI [" << uriAnonym << "]:\n"
<< httpRequest.header());
const std::shared_ptr<const http::Response> httpResponse
= httpSession->syncRequest(httpRequest);
callDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - startTime);
const http::StatusCode statusCode = httpResponse->statusLine().statusCode();
if (statusCode == http::StatusCode::MovedPermanently ||
statusCode == http::StatusCode::Found ||
statusCode == http::StatusCode::TemporaryRedirect ||
statusCode == http::StatusCode::PermanentRedirect)
{
if (redirectLimit)
{
const std::string& location = httpResponse->get("Location");
LOG_TRC("WOPI::CheckFileInfo redirect to URI [" << COOLWSD::anonymizeUrl(location) << "]");
Poco::URI redirectUriObject(location);
setUri(redirectUriObject);
return getWOPIFileInfoForUri(redirectUriObject, auth, lockCtx, redirectLimit - 1);
}
else
{
LOG_WRN("WOPI::CheckFileInfo redirected too many times - URI [" << uriAnonym << "]");
}
}
// Note: we don't log the response if obfuscation is enabled, except for failures.
wopiResponse = httpResponse->getBody();
const bool failed = (httpResponse->statusLine().statusCode() != http::StatusCode::OK);
Log::StreamLogger logRes = failed ? Log::error() : Log::trace();
if (logRes.enabled())
{
logRes << "WOPI::CheckFileInfo " << (failed ? "failed" : "returned") << " for URI ["
<< uriAnonym << "]: " << httpResponse->statusLine().statusCode() << ' '
<< httpResponse->statusLine().reasonPhrase()
<< ". Headers: " << httpResponse->header()
<< (failed ? "\tBody: [" + wopiResponse + ']' : std::string());
LOG_END_FLUSH(logRes);
}
if (failed)
{
if (httpResponse->statusLine().statusCode() == http::StatusCode::Forbidden)
throw UnauthorizedRequestException(
"Access denied, 403. WOPI::CheckFileInfo failed on: " + uriAnonym);
throw StorageConnectionException("WOPI::CheckFileInfo failed: " + wopiResponse);
}
}
catch (const Poco::Exception& pexc)
{
LOG_ERR("Cannot get file info from WOPI storage uri [" << uriAnonym << "]. Error: " <<
pexc.displayText() << (pexc.nested() ? " (" + pexc.nested()->displayText() + ')' : ""));
throw;
}
catch (const BadRequestException& exc)
{
LOG_ERR("Cannot get file info from WOPI storage uri [" << uriAnonym << "]. Error: " << exc.what());
}
Poco::JSON::Object::Ptr object;
if (JsonUtil::parseJSON(wopiResponse, object))
{
if (COOLWSD::AnonymizeUserData)
LOG_DBG("WOPI::CheckFileInfo (" << callDurationMs << "): anonymizing...");
else
LOG_DBG("WOPI::CheckFileInfo (" << callDurationMs << "): " << wopiResponse);
std::size_t size = 0;
std::string filename, ownerId, lastModifiedTime;
JsonUtil::findJSONValue(object, "Size", size);
JsonUtil::findJSONValue(object, "OwnerId", ownerId);
JsonUtil::findJSONValue(object, "BaseFileName", filename);
JsonUtil::findJSONValue(object, "LastModifiedTime", lastModifiedTime);
FileInfo fileInfo = FileInfo({filename, ownerId, lastModifiedTime});
setFileInfo(fileInfo);
if (COOLWSD::AnonymizeUserData)
Util::mapAnonymized(Util::getFilenameFromURL(filename), Util::getFilenameFromURL(getUri().toString()));
auto wopiInfo = std::make_unique<WopiStorage::WOPIFileInfo>(fileInfo, object, uriObject);
if (wopiInfo->getSupportsLocks())
lockCtx.initSupportsLocks();
// If FileUrl is set, we use it for GetFile.
_fileUrl = wopiInfo->getFileUrl();
return wopiInfo;
}
else
{
if (COOLWSD::AnonymizeUserData)
wopiResponse = "obfuscated";
LOG_ERR("WOPI::CheckFileInfo ("
<< callDurationMs
<< ") failed or no valid JSON payload returned. Access denied. Original response: ["
<< wopiResponse << "].");
throw UnauthorizedRequestException("Access denied. WOPI::CheckFileInfo failed on: " + uriAnonym);
}
}
std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Authorization& auth,
LockContext& lockCtx)
{
return getWOPIFileInfoForUri(getUri(), auth, lockCtx, RedirectionLimit);
// If FileUrl is set, we use it for GetFile.
_fileUrl = wopiFileInfo.getFileUrl();
}
WopiStorage::WOPIFileInfo::WOPIFileInfo(const FileInfo& fileInfo,

View File

@ -704,17 +704,11 @@ public:
<< COOLWSD::anonymizeUrl(uri.toString()) << ']');
}
/// Returns the response of CheckFileInfo WOPI call for URI that was
/// provided during the initial creation of the WOPI storage.
/// Handles the response from CheckFileInfo, as converted into WOPIFileInfo.
/// Also extracts the basic file information from the response
/// which can then be obtained using getFileInfo()
/// Also sets up the locking context for future operations.
std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const Authorization& auth, LockContext& lockCtx);
/// Implementation of getWOPIFileInfo for specific URI
std::unique_ptr<WOPIFileInfo> getWOPIFileInfoForUri(Poco::URI uriObject,
const Authorization& auth,
LockContext& lockCtx,
unsigned redirectLimit);
void handleWOPIFileInfo(const WOPIFileInfo& wopiFileInfo, LockContext& lockCtx);
/// Update the locking state (check-in/out) of the associated file
LockUpdateResult updateLockState(const Authorization& auth, LockContext& lockCtx, bool lock,