# HG changeset patch # User Sebastien Jodogne # Date 1559840067 -7200 # Node ID 0ce9b4f5fdf5e499461c2161e3c8d680a59da745 # Parent 2f82ef41bf5a7e530e1a8876aca5f2d4abc01fb0 new abstraction: IHttpHandler::CreateStreamHandler() diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 Core/HttpServer/EmbeddedResourceHttpHandler.h --- a/Core/HttpServer/EmbeddedResourceHttpHandler.h Thu Jun 06 16:42:41 2019 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Thu Jun 06 18:54:27 2019 +0200 @@ -51,6 +51,16 @@ const std::string& baseUri, EmbeddedResources::DirectoryResourceId resourceId); + virtual IStream* CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers) + { + return NULL; + } + virtual bool Handle( HttpOutput& output, RequestOrigin origin, diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 Core/HttpServer/FilesystemHttpHandler.h --- a/Core/HttpServer/FilesystemHttpHandler.h Thu Jun 06 16:42:41 2019 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.h Thu Jun 06 18:54:27 2019 +0200 @@ -53,6 +53,16 @@ FilesystemHttpHandler(const std::string& baseUri, const std::string& root); + virtual IStream* CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers) + { + return NULL; + } + virtual bool Handle( HttpOutput& output, RequestOrigin origin, diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 Core/HttpServer/HttpServer.cpp --- a/Core/HttpServer/HttpServer.cpp Thu Jun 06 16:42:41 2019 +0200 +++ b/Core/HttpServer/HttpServer.cpp Thu Jun 06 18:54:27 2019 +0200 @@ -79,8 +79,8 @@ namespace Orthanc { - static const char multipart[] = "multipart/form-data; boundary="; - static unsigned int multipartLength = sizeof(multipart) / sizeof(char) - 1; + static const char MULTIPART_FORM[] = "multipart/form-data; boundary="; + static unsigned int MULTIPART_FORM_LENGTH = sizeof(MULTIPART_FORM) / sizeof(char) - 1; namespace @@ -302,16 +302,60 @@ } - - static PostDataStatus ReadBody(std::string& postData, - struct mg_connection *connection, - const IHttpHandler::Arguments& headers) + static PostDataStatus ReadBodyWithContentLength(std::string& body, + struct mg_connection *connection, + const std::string& contentLength) { - IHttpHandler::Arguments::const_iterator cs = headers.find("content-length"); - if (cs == headers.end()) + int length; + try + { + length = boost::lexical_cast(contentLength); + } + catch (boost::bad_lexical_cast&) + { + return PostDataStatus_NoLength; + } + + if (length < 0) + { + length = 0; + } + + body.resize(length); + + size_t pos = 0; + while (length > 0) { - // Store all the individual chunks within a temporary file, then - // read it back into the memory buffer "postData" + int r = mg_read(connection, &body[pos], length); + if (r <= 0) + { + return PostDataStatus_Failure; + } + + assert(r <= length); + length -= r; + pos += r; + } + + return PostDataStatus_Success; + } + + + static PostDataStatus ReadBodyToString(std::string& body, + struct mg_connection *connection, + const IHttpHandler::Arguments& headers) + { + IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length"); + + if (contentLength != headers.end()) + { + // "Content-Length" is available + return ReadBodyWithContentLength(body, connection, contentLength->second); + } + else + { + // No Content-Length. Store the individual chunks in a temporary + // file, then read it back into the memory buffer "body" FileBuffer buffer; std::string tmp(1024 * 1024, 0); @@ -333,42 +377,54 @@ } } - buffer.Read(postData); + buffer.Read(body); return PostDataStatus_Success; } + } + + + static PostDataStatus ReadBodyToStream(IHttpHandler::IStream& stream, + struct mg_connection *connection, + const IHttpHandler::Arguments& headers) + { + IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length"); + + if (contentLength != headers.end()) + { + // "Content-Length" is available + + std::string body; + PostDataStatus status = ReadBodyWithContentLength(body, connection, contentLength->second); + + if (status == PostDataStatus_Success && + !body.empty()) + { + stream.AddBodyChunk(body.c_str(), body.size()); + } + + return status; + } else { - // "Content-Length" is available - int length; - try - { - length = boost::lexical_cast(cs->second); - } - catch (boost::bad_lexical_cast&) + // No Content-Length. Stream the HTTP connection. + std::string tmp(1024 * 1024, 0); + + for (;;) { - return PostDataStatus_NoLength; - } - - if (length < 0) - { - length = 0; - } - - postData.resize(length); - - size_t pos = 0; - while (length > 0) - { - int r = mg_read(connection, &postData[pos], length); - if (r <= 0) + int r = mg_read(connection, &tmp[0], tmp.size()); + if (r < 0) { return PostDataStatus_Failure; } - - assert(r <= length); - length -= r; - pos += r; + else if (r == 0) + { + break; + } + else + { + stream.AddBodyChunk(tmp.c_str(), r); + } } return PostDataStatus_Success; @@ -376,17 +432,16 @@ } - - static PostDataStatus ParseMultipartPost(std::string &completedFile, + static PostDataStatus ParseMultipartForm(std::string &completedFile, struct mg_connection *connection, const IHttpHandler::Arguments& headers, const std::string& contentType, ChunkStore& chunkStore) { - std::string boundary = "--" + contentType.substr(multipartLength); + std::string boundary = "--" + contentType.substr(MULTIPART_FORM_LENGTH); - std::string postData; - PostDataStatus status = ReadBody(postData, connection, headers); + std::string body; + PostDataStatus status = ReadBodyToString(body, connection, headers); if (status != PostDataStatus_Success) { @@ -433,7 +488,7 @@ { FindIterator last; for (FindIterator it = - make_find_iterator(postData, boost::first_finder(boundary)); + make_find_iterator(body, boost::first_finder(boundary)); it!=FindIterator(); ++it) { @@ -752,7 +807,25 @@ } - // Extract the body of the request for PUT and POST + // Decompose the URI into its components + UriComponents uri; + try + { + Toolbox::SplitUriComponents(uri, requestUri); + } + catch (OrthancException&) + { + output.SendStatus(HttpStatus_400_BadRequest); + return; + } + + LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); + + + bool found = false; + + // Extract the body of the request for PUT and POST, or process + // the body as a stream // TODO Avoid unneccessary memcopy of the body @@ -762,23 +835,39 @@ { PostDataStatus status; + bool isMultipartForm = false; + IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); - if (ct == headers.end()) + if (ct != headers.end() && + ct->second.size() >= MULTIPART_FORM_LENGTH && + !memcmp(ct->second.c_str(), MULTIPART_FORM, MULTIPART_FORM_LENGTH)) { - // No content-type specified. Assume no multi-part content occurs at this point. - status = ReadBody(body, connection, headers); + status = ParseMultipartForm(body, connection, headers, ct->second, server.GetChunkStore()); + isMultipartForm = true; } - else + + if (!isMultipartForm) { - std::string contentType = ct->second; - if (contentType.size() >= multipartLength && - !memcmp(contentType.c_str(), multipart, multipartLength)) + std::auto_ptr stream; + + if (server.HasHandler()) { - status = ParseMultipartPost(body, connection, headers, contentType, server.GetChunkStore()); + stream.reset(server.GetHandler().CreateStreamHandler + (RequestOrigin_RestApi, remoteIp, username.c_str(), method, uri, headers)); + } + + if (stream.get() != NULL) + { + status = ReadBodyToStream(*stream, connection, headers); + + if (status == PostDataStatus_Success) + { + stream->Execute(output); + } } else { - status = ReadBody(body, connection, headers); + status = ReadBodyToString(body, connection, headers); } } @@ -796,30 +885,17 @@ output.AnswerEmpty(); return; + case PostDataStatus_Success: + break; + default: - break; + throw OrthancException(ErrorCode_InternalError); } } - // Decompose the URI into its components - UriComponents uri; - try - { - Toolbox::SplitUriComponents(uri, requestUri); - } - catch (OrthancException&) - { - output.SendStatus(HttpStatus_400_BadRequest); - return; - } - - - LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); - - bool found = false; - - if (server.HasHandler()) + if (!found && + server.HasHandler()) { found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), method, uri, headers, argumentsGET, body.c_str(), body.size()); diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 Core/HttpServer/IHttpHandler.h --- a/Core/HttpServer/IHttpHandler.h Thu Jun 06 16:42:41 2019 +0200 +++ b/Core/HttpServer/IHttpHandler.h Thu Jun 06 18:54:27 2019 +0200 @@ -49,10 +49,34 @@ typedef std::map Arguments; typedef std::vector< std::pair > GetArguments; + + class IStream : public boost::noncopyable + { + public: + virtual ~IStream() + { + } + + virtual void AddBodyChunk(const void* data, + size_t size) = 0; + + virtual void Execute(HttpOutput& output) = 0; + }; + + virtual ~IHttpHandler() { } + // This function is called for each incomding POST/PUT request + // (new in Orthanc 1.5.7). It allows to deal with chunked transfers. + virtual IStream* CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers) = 0; + virtual bool Handle(HttpOutput& output, RequestOrigin origin, const char* remoteIp, diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 Core/RestApi/RestApi.h --- a/Core/RestApi/RestApi.h Thu Jun 06 16:42:41 2019 +0200 +++ b/Core/RestApi/RestApi.h Thu Jun 06 18:54:27 2019 +0200 @@ -47,6 +47,16 @@ public: static void AutoListChildren(RestApiGetCall& call); + virtual IStream* CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers) + { + return NULL; + } + virtual bool Handle(HttpOutput& output, RequestOrigin origin, const char* remoteIp, diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 OrthancServer/OrthancHttpHandler.cpp --- a/OrthancServer/OrthancHttpHandler.cpp Thu Jun 06 16:42:41 2019 +0200 +++ b/OrthancServer/OrthancHttpHandler.cpp Thu Jun 06 18:54:27 2019 +0200 @@ -39,6 +39,28 @@ namespace Orthanc { + IHttpHandler::IStream* OrthancHttpHandler::CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers) + { + for (Handlers::const_iterator it = handlers_.begin(); it != handlers_.end(); ++it) + { + IHttpHandler::IStream* stream = (*it)->CreateStreamHandler( + origin, remoteIp, username, method, uri, headers); + + if (stream != NULL) + { + return stream; + } + } + + return NULL; + } + + bool OrthancHttpHandler::Handle(HttpOutput& output, RequestOrigin origin, const char* remoteIp, @@ -50,16 +72,16 @@ const char* bodyData, size_t bodySize) { - bool found = false; - - for (Handlers::const_iterator it = handlers_.begin(); - it != handlers_.end() && !found; ++it) + for (Handlers::const_iterator it = handlers_.begin(); it != handlers_.end(); ++it) { - found = (*it)->Handle(output, origin, remoteIp, username, method, uri, - headers, getArguments, bodyData, bodySize); + if ((*it)->Handle(output, origin, remoteIp, username, method, uri, + headers, getArguments, bodyData, bodySize)) + { + return true; + } } - return found; + return false; } diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 OrthancServer/OrthancHttpHandler.h --- a/OrthancServer/OrthancHttpHandler.h Thu Jun 06 16:42:41 2019 +0200 +++ b/OrthancServer/OrthancHttpHandler.h Thu Jun 06 18:54:27 2019 +0200 @@ -50,6 +50,13 @@ { } + virtual IStream* CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers); + virtual bool Handle(HttpOutput& output, RequestOrigin origin, const char* remoteIp, diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Thu Jun 06 16:42:41 2019 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu Jun 06 18:54:27 2019 +0200 @@ -4009,4 +4009,17 @@ } } } + + + IHttpHandler::IStream* OrthancPlugins::CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers) + { + // TODO - Plugins to install a handler for multipart body. + + return NULL; + } } diff -r 2f82ef41bf5a -r 0ce9b4f5fdf5 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Thu Jun 06 16:42:41 2019 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu Jun 06 18:54:27 2019 +0200 @@ -321,6 +321,14 @@ const Json::Value& value); void RefreshMetrics(); + + // New in Orthanc 1.5.7 + virtual IStream* CreateStreamHandler(RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers); }; }