changeset 3395:0ce9b4f5fdf5

new abstraction: IHttpHandler::CreateStreamHandler()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 06 Jun 2019 18:54:27 +0200
parents 2f82ef41bf5a
children 4981405e6c5c
files Core/HttpServer/EmbeddedResourceHttpHandler.h Core/HttpServer/FilesystemHttpHandler.h Core/HttpServer/HttpServer.cpp Core/HttpServer/IHttpHandler.h Core/RestApi/RestApi.h OrthancServer/OrthancHttpHandler.cpp OrthancServer/OrthancHttpHandler.h Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h
diffstat 9 files changed, 259 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- 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,
--- 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,
--- 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<int>(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<int>(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<IHttpHandler::IStream> 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());
--- 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<std::string, std::string>                  Arguments;
     typedef std::vector< std::pair<std::string, std::string> >  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,
--- 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,
--- 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;
   }
 
 
--- 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,
--- 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;
+  }
 }
--- 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);
   };
 }