Mercurial > hg > orthanc
changeset 5953:684d49e47b5e default tip
added OrthancPluginStartStreamAnswer and OrthancPluginSendStreamChunk to allow sending HTTP response by chunks
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Fri, 10 Jan 2025 18:27:27 +0100 |
parents | bfadfbcca13e |
children | |
files | NEWS OrthancFramework/Sources/HttpServer/HttpOutput.cpp OrthancFramework/Sources/HttpServer/HttpOutput.h OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h |
diffstat | 5 files changed, 200 insertions(+), 26 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Wed Jan 08 15:18:00 2025 +0100 +++ b/NEWS Fri Jan 10 18:27:27 2025 +0100 @@ -15,6 +15,13 @@ the SOPInstanceUID untouched in this case. +Plugins +------- + +* SDK: added OrthancPluginStartStreamAnswer and OrthancPluginSendStreamChunk to allow + sending HTTP response by chunks. + + Version 1.12.5 (2024-12-17) ===========================
--- a/OrthancFramework/Sources/HttpServer/HttpOutput.cpp Wed Jan 08 15:18:00 2025 +0100 +++ b/OrthancFramework/Sources/HttpServer/HttpOutput.cpp Fri Jan 10 18:27:27 2025 +0100 @@ -474,6 +474,10 @@ return stateMachine_.GetState() == StateMachine::State_WritingMultipart; } + bool HttpOutput::IsWritingStream() const + { + return stateMachine_.GetState() == StateMachine::State_WritingStream; + } void HttpOutput::Answer(const void* buffer, size_t length) @@ -977,4 +981,21 @@ stateMachine_.CloseStream(); } + + void HttpOutput::StartStream(const std::string& contentType) + { + stateMachine_.StartStream(contentType.c_str()); + } + + void HttpOutput::SendStreamItem(const void* data, + size_t size) + { + stateMachine_.SendStreamItem(data, size); + } + + void HttpOutput::CloseStream() + { + stateMachine_.CloseStream(); + } + }
--- a/OrthancFramework/Sources/HttpServer/HttpOutput.h Wed Jan 08 15:18:00 2025 +0100 +++ b/OrthancFramework/Sources/HttpServer/HttpOutput.h Fri Jan 10 18:27:27 2025 +0100 @@ -224,5 +224,14 @@ * used to handle compression using "Content-Encoding". **/ void AnswerWithoutBuffering(IHttpStreamAnswer& stream); + + void StartStream(const std::string& contentType); + + void SendStreamItem(const void* data, + size_t size); + + void CloseStream(); + + bool IsWritingStream() const; }; }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Jan 08 15:18:00 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Fri Jan 10 18:27:27 2025 +0100 @@ -1101,40 +1101,67 @@ class PluginHttpOutput : public boost::noncopyable { private: - enum MultipartState - { - MultipartState_None, - MultipartState_FirstPart, - MultipartState_SecondPart, - MultipartState_NextParts + // enum MultipartState + // { + // MultipartState_None, + // MultipartState_FirstPart, + // MultipartState_SecondPart, + // MultipartState_NextParts + // }; + + // enum StreamState + // { + // StreamState_None, + // StreamState_Opened + // }; + + enum State + { + State_None, + State_MultipartFirstPart, + State_MultipartSecondPart, + State_MultipartNextParts, + State_WritingStream + }; + // enum StreamState + // { + // StreamState_None, + // StreamState_Opened + // }; + HttpOutput& output_; std::unique_ptr<std::string> errorDetails_; bool logDetails_; - MultipartState multipartState_; + // MultipartState multipartState_; std::string multipartSubType_; std::string multipartContentType_; std::string multipartFirstPart_; std::map<std::string, std::string> multipartFirstHeaders_; - + // StreamState streamState_; + State state_; + public: explicit PluginHttpOutput(HttpOutput& output) : output_(output), logDetails_(false), - multipartState_(MultipartState_None) + state_(State_None) + // multipartState_(MultipartState_None), + // streamState_(StreamState_None) { } HttpOutput& GetOutput() { - if (multipartState_ == MultipartState_None) + // if (multipartState_ == MultipartState_None) + if (state_ == State_None) { return output_; } else { - // Must use "SendMultipartItem()" on multipart streams + // Must use "SendMultipartItem()" on multipart streams or SendStreamChunk throw OrthancException(ErrorCode_BadSequenceOfCalls); } } @@ -1171,18 +1198,47 @@ void StartMultipart(const char* subType, const char* contentType) { - if (multipartState_ != MultipartState_None) + // if (multipartState_ != MultipartState_None) + if (state_ != State_None) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } else { - multipartState_ = MultipartState_FirstPart; + // multipartState_ = MultipartState_FirstPart; + state_ = State_MultipartFirstPart; multipartSubType_ = subType; multipartContentType_ = contentType; } } + void StartStream(const char* contentType) + { + if (state_ != State_None) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + output_.StartStream(contentType); + state_ = State_WritingStream; + } + } + + void SendStreamItem(const void* data, + size_t size) + { + if (state_ != State_WritingStream) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + output_.SendStreamItem(data, size); + } + } + + void SendMultipartItem(const void* data, size_t size, const std::map<std::string, std::string>& headers) @@ -1192,19 +1248,19 @@ throw OrthancException(ErrorCode_NullPointer); } - switch (multipartState_) + switch (state_) { - case MultipartState_None: + case State_None: // Must call "StartMultipart()" before throw OrthancException(ErrorCode_BadSequenceOfCalls); - case MultipartState_FirstPart: + case State_MultipartFirstPart: multipartFirstPart_.assign(reinterpret_cast<const char*>(data), size); multipartFirstHeaders_ = headers; - multipartState_ = MultipartState_SecondPart; + state_ = State_MultipartSecondPart; break; - case MultipartState_SecondPart: + case State_MultipartSecondPart: // Start an actual stream for chunked transfer as soon as // there are more than 2 elements in the multipart stream output_.StartMultipart(multipartSubType_, multipartContentType_); @@ -1213,10 +1269,10 @@ multipartFirstPart_.clear(); // Release memory output_.SendMultipartItem(data, size, headers); - multipartState_ = MultipartState_NextParts; + state_ = State_MultipartNextParts; break; - case MultipartState_NextParts: + case State_MultipartNextParts: output_.SendMultipartItem(data, size, headers); break; @@ -1230,21 +1286,21 @@ { if (error == OrthancPluginErrorCode_Success) { - switch (multipartState_) + switch (state_) { - case MultipartState_None: + case State_None: assert(!output_.IsWritingMultipart()); break; - case MultipartState_FirstPart: // Multipart started, but no part was sent - case MultipartState_SecondPart: // Multipart started, first part is pending + case State_MultipartFirstPart: // Multipart started, but no part was sent + case State_MultipartSecondPart: // Multipart started, first part is pending { assert(!output_.IsWritingMultipart()); std::vector<const void*> parts; std::vector<size_t> sizes; std::vector<const std::map<std::string, std::string>*> headers; - if (multipartState_ == MultipartState_SecondPart) + if (state_ == State_MultipartSecondPart) { parts.push_back(multipartFirstPart_.c_str()); sizes.push_back(multipartFirstPart_.size()); @@ -1256,10 +1312,14 @@ break; } - case MultipartState_NextParts: + case State_MultipartNextParts: assert(output_.IsWritingMultipart()); output_.CloseMultipart(); + case State_WritingStream: + assert(output_.IsWritingStream()); + output_.CloseStream(); + default: throw OrthancException(ErrorCode_InternalError); } @@ -4892,6 +4952,21 @@ ApplySendMultipartItem2(parameters); return true; + case _OrthancPluginService_StartStreamAnswer: + { + const _OrthancPluginStartStreamAnswer& p = + *reinterpret_cast<const _OrthancPluginStartStreamAnswer*>(parameters); + reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->StartStream(p.contentType); + return true; + } + + case _OrthancPluginService_SendStreamChunk: + { + const _OrthancPluginAnswerBuffer& p = + *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters); + reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendStreamItem(p.answer, p.answerSize); + return true; + } case _OrthancPluginService_ReadFile: { const _OrthancPluginReadFile& p =
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed Jan 08 15:18:00 2025 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jan 10 18:27:27 2025 +0100 @@ -507,6 +507,8 @@ _OrthancPluginService_CompressAndAnswerImage = 2011, _OrthancPluginService_SendMultipartItem2 = 2012, _OrthancPluginService_SetHttpErrorDetails = 2013, + _OrthancPluginService_StartStreamAnswer = 2014, + _OrthancPluginService_SendStreamChunk = 2015, /* Access to the Orthanc database and API */ _OrthancPluginService_GetDicomForInstance = 3000, @@ -9568,6 +9570,66 @@ } + typedef struct + { + OrthancPluginRestOutput* output; + const char* contentType; + } _OrthancPluginStartStreamAnswer; + + /** + * @brief Start an HTTP stream answer. + * + * Initiates an HTTP stream answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param contentType The MIME type of the items in the stream answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendStreamChunk() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartStreamAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* contentType) + { + _OrthancPluginStartStreamAnswer params; + params.output = output; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartStreamAnswer, ¶ms); + } + + + /** + * @brief Send a chunk as a part of an HTTP stream answer. + * + * This function sends a chunk as part of an HTTP stream + * answer that was initiated by OrthancPluginStartStreamAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginStartStreamAnswer() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendStreamChunk( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const void* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendStreamChunk, ¶ms); + } + + #ifdef __cplusplus } #endif