/*! * @project XRootD SSI/Protocol Buffer Interface Project * @brief XRootD SSI Responder class template * @copyright Copyright 2018 CERN * @license This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include #include "XrdSsiPbException.hpp" #include "XrdSsiPbAlert.hpp" namespace XrdSsiPb { /*! * Exception Handler class. * * This is used to send framework exceptions back to the client. The client should specialize on this * class for the Response class. */ template class ExceptionHandler { public: void operator()(MetadataType &response, const ExceptionType &ex); }; /*! * Request Processing class. * * This is an agent object that the Service object creates for each Request that it receives. The Request * object will be bound to the XrdSsiResponder object via a call to XrdSsiResponder::BindRequest(). Once * the relationship is established, the XrdSsi framework keeps track of the Request object and manages * its lifetime. * * The XrdSsiResponder class contains the methods needed to interact with the Request object: get the * Request, release storage, send Alerts, and post a Response. It also knows how to safely interact with * the Request object, handling asynchronous requests such as cancellation, broken TCP connections, etc. */ template class RequestProc : public XrdSsiResponder { public: RequestProc(XrdSsiResource &resource) : m_resource(resource), m_response_stream_ptr(nullptr) { Log::Msg(Log::DEBUG, LOG_SUFFIX, "Called RequestProc() constructor"); } virtual ~RequestProc() { Log::Msg(Log::DEBUG, LOG_SUFFIX, "Called ~RequestProc() destructor"); } void Execute(); virtual void Finished(XrdSsiRequest &rqstR, const XrdSsiRespInfo &rInfo, bool cancel=false) override; private: /*! * Encapsulate the Alert protocol buffer inside a XrdSsiRespInfoMsg object. * * Alert message objects are created on the heap with lifetime managed by the XrdSsiResponder class. */ void Alert(const AlertType &alert) { XrdSsiResponder::Alert(*(new AlertMsg(alert))); } /*! * Handle bad protocol buffer Requests. * * This class should store the exception in the Response Protocol Buffer, which the framework will * send back to the client. The client needs to define the specialized version of this class. */ ExceptionHandler Throw; /*! * Execute action after deserialization of the Request Protocol Buffer. * * The client needs to define the specialized version of this method. */ void ExecuteAction(); // Member variables const XrdSsiResource &m_resource; //!< Resource associated with the Request std::promise m_promise; //!< Promise that the Request has been processed /* * Protocol Buffer members * * The Serialized Metadata Response buffer needs to be a member variable as it must stay in scope * after calling RequestProc(), until Finished() is called. * * The maximum amount of metadata that may be sent is defined by XrdSsiResponder::MaxMetaDataSZ * constant member. */ RequestType m_request; //!< Request object MetadataType m_metadata; //!< Metadata Response object std::string m_metadata_str; //!< Serialized Metadata Response buffer std::string m_response_str; //!< Serialized Data Response buffer XrdSsiStream *m_response_stream_ptr; //!< Stream Response pointer static constexpr const char* const LOG_SUFFIX = "Pb::RequestProc"; //!< Identifier for log messages }; template void RequestProc::Execute() { Log::Msg(Log::DEBUG, LOG_SUFFIX, "Called Execute()"); // Deserialize the Request int request_len; const char *request_buffer = GetRequest(request_len); Log::Msg(Log::DEBUG, LOG_SUFFIX, "RequestProc(): received ", request_len, " bytes"); Log::DumpBuffer(Log::PROTORAW, request_buffer, request_len); if(m_request.ParseFromArray(request_buffer, request_len)) { Log::DumpProtobuf(Log::PROTOBUF, &m_request); // Pass control from the framework to the application ExecuteAction(); } else { // Pass an exception back to the client and continue processing Throw(m_metadata, PbException("m_request.ParseFromArray() failed")); } // Release the request buffer ReleaseRequestBuffer(); // Serialize and send the Metadata Log::Msg(Log::PROTOBUF, LOG_SUFFIX, "RequestProc(): sending metadata:"); Log::DumpProtobuf(Log::PROTOBUF, &m_metadata); if(!m_metadata.SerializeToString(&m_metadata_str)) { throw PbException("m_metadata.SerializeToString() failed"); } Log::DumpBuffer(Log::PROTORAW, m_metadata_str.c_str(), m_metadata_str.size()); SetMetadata(m_metadata_str.c_str(), m_metadata_str.size()); // Send the Response if(m_response_stream_ptr != nullptr) { // Stream Response SetResponse(m_response_stream_ptr); } else if(m_response_str.size() != 0) { // Data Response Log::Msg(Log::PROTORAW, LOG_SUFFIX, "RequestProc(): sending Data response:"); Log::DumpBuffer(Log::PROTORAW, m_response_str.c_str(), m_response_str.size()); SetResponse(m_response_str.c_str(), m_response_str.size()); } else { // Metadata-only Response // // It is necessary to set a Response even for empty responses, otherwise Finished() // will not be called on the Request. SetNilResponse(); } // Wait for the framework to call Finished() auto finished = m_promise.get_future(); finished.wait(); } /*! * Clean up the Request Processing object. * * This is called when the Request has been processed or cancelled. * * If required, you can create specialized versions of this method to handle cancellation/cleanup for * specific message types. */ template void RequestProc::Finished(XrdSsiRequest &rqstR, const XrdSsiRespInfo &rInfo, bool cancel) { Log::Msg(Log::DEBUG, LOG_SUFFIX, "Called Finished()"); if(cancel) { // Reclaim resources dedicated to the request and tell caller the request object can be reclaimed Log::Msg(Log::WARNING, LOG_SUFFIX, "Request timed out or was cancelled"); } else { // Reclaim any allocated resources } // Delete the stream object (if there is one for this request) delete m_response_stream_ptr; // Tell Execute() that we have Finished m_promise.set_value(); } } // namespace XrdSsiPb