changeset 1113:ba5c0908600c

Refactoring of HttpOutput ("Content-Length" header is now always sent)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 02 Sep 2014 15:51:20 +0200
parents a119f9ae3640
children adfd2c7a92f3
files Core/HttpServer/BufferHttpSender.h Core/HttpServer/EmbeddedResourceHttpHandler.cpp Core/HttpServer/FilesystemHttpHandler.cpp Core/HttpServer/FilesystemHttpSender.cpp Core/HttpServer/HttpFileSender.cpp Core/HttpServer/HttpOutput.cpp Core/HttpServer/HttpOutput.h Core/HttpServer/MongooseServer.cpp Core/RestApi/RestApi.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApiOutput.h NEWS OrthancServer/ParsedDicomFile.cpp OrthancServer/ServerIndex.cpp Plugins/Engine/PluginsHttpHandler.cpp
diffstat 15 files changed, 303 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/Core/HttpServer/BufferHttpSender.h	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/BufferHttpSender.h	Tue Sep 02 15:51:20 2014 +0200
@@ -50,7 +50,7 @@
     {
       if (buffer_.size())
       {
-        output.SendBodyData(&buffer_[0], buffer_.size());
+        output.SendBody(&buffer_[0], buffer_.size());
       }
 
       return true;
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -78,12 +78,14 @@
     {
       const void* buffer = EmbeddedResources::GetDirectoryResourceBuffer(resourceId_, resourcePath.c_str());
       size_t size = EmbeddedResources::GetDirectoryResourceSize(resourceId_, resourcePath.c_str());
-      output.AnswerBufferWithContentType(buffer, size, contentType);
+
+      output.SetContentType(contentType.c_str());
+      output.SendBody(buffer, size);
     }
     catch (OrthancException&)
     {
       LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath;
-      output.SendHeader(HttpStatus_404_NotFound);
+      output.SendStatus(HttpStatus_404_NotFound);
     }
 
     return true;
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -55,16 +55,18 @@
   {
     namespace fs = boost::filesystem;
 
-    output.SendOkHeader("text/html", false, 0, NULL);
-    output.SendBodyString("<html>");
-    output.SendBodyString("  <body>");
-    output.SendBodyString("    <h1>Subdirectories</h1>");
-    output.SendBodyString("    <ul>");
+    output.SetContentType("text/html");
+
+    std::string s;
+    s += "<html>";
+    s += "  <body>";
+    s += "    <h1>Subdirectories</h1>";
+    s += "    <ul>";
 
     if (uri.size() > 0)
     {
       std::string h = Toolbox::FlattenUri(uri) + "/..";
-      output.SendBodyString("<li><a href=\"" + h + "\">..</a></li>");
+      s += "<li><a href=\"" + h + "\">..</a></li>";
     }
 
     fs::directory_iterator end;
@@ -78,12 +80,12 @@
 
       std::string h = Toolbox::FlattenUri(uri) + "/" + f;
       if (fs::is_directory(it->status()))
-        output.SendBodyString("<li><a href=\"" + h + "\">" + f + "</a></li>");
+        s += "<li><a href=\"" + h + "\">" + f + "</a></li>";
     }      
 
-    output.SendBodyString("    </ul>");      
-    output.SendBodyString("    <h1>Files</h1>");
-    output.SendBodyString("    <ul>");
+    s += "    </ul>";      
+    s += "    <h1>Files</h1>";
+    s += "    <ul>";
 
     for (fs::directory_iterator it(p) ; it != end; ++it)
     {
@@ -95,12 +97,14 @@
 
       std::string h = Toolbox::FlattenUri(uri) + "/" + f;
       if (fs::is_regular_file(it->status()))
-        output.SendBodyString("<li><a href=\"" + h + "\">" + f + "</a></li>");
+        s += "<li><a href=\"" + h + "\">" + f + "</a></li>";
     }      
 
-    output.SendBodyString("    </ul>");
-    output.SendBodyString("  </body>");
-    output.SendBodyString("</html>");
+    s += "    </ul>";
+    s += "  </body>";
+    s += "</html>";
+
+    output.SendBody(s);
   }
 
 
@@ -162,7 +166,7 @@
     }
     else
     {
-      output.SendHeader(HttpStatus_404_NotFound);
+      output.SendStatus(HttpStatus_404_NotFound);
     }
 
     return true;
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -73,7 +73,7 @@
       }
       else
       {
-        output.SendBodyData(&buffer[0], nbytes);
+        output.SendBody(&buffer[0], nbytes);
       }
     }
 
--- a/Core/HttpServer/HttpFileSender.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/HttpFileSender.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -33,13 +33,25 @@
 #include "../PrecompiledHeaders.h"
 #include "HttpFileSender.h"
 
+#include "../OrthancException.h"
+
 #include <boost/lexical_cast.hpp>
 
 namespace Orthanc
 {
   void HttpFileSender::SendHeader(HttpOutput& output)
   {
-    output.SendOkHeader(contentType_.c_str(), true, GetFileSize(), downloadFilename_.c_str());
+    if (contentType_.size() > 0)
+    {
+      output.SetContentType(contentType_.c_str());
+    }
+
+    if (downloadFilename_.size() > 0)
+    {
+      output.SetContentFilename(downloadFilename_.c_str());
+    }
+
+    output.SetContentLength(GetFileSize());
   }
 
   void HttpFileSender::Send(HttpOutput& output)
@@ -48,7 +60,8 @@
 
     if (!SendData(output))
     {
-      output.SendHeader(HttpStatus_500_InternalServerError);
+      throw OrthancException(ErrorCode_InternalError);
+      //output.SendHeader(HttpStatus_500_InternalServerError);
     }
   }
 }
--- a/Core/HttpServer/HttpOutput.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -43,181 +43,224 @@
 
 namespace Orthanc
 {
-  void HttpOutput::StateMachine::SendHttpStatus(HttpStatus status)
+  HttpOutput::StateMachine::StateMachine(IHttpOutputStream& stream) : 
+    stream_(stream),
+    state_(State_WritingHeader),
+    status_(HttpStatus_200_Ok),
+    hasContentLength_(false),
+    contentPosition_(0)
+  {
+  }
+
+  HttpOutput::StateMachine::~StateMachine()
   {
-    if (state_ != State_WaitingHttpStatus)
+    if (state_ != State_Done)
+    {
+      //asm volatile ("int3;");
+      LOG(ERROR) << "This HTTP answer does not contain any body";
+    }
+
+    if (hasContentLength_ && contentPosition_ != contentLength_)
     {
-      LOG(ERROR) << "Sending twice an HTTP status";
-      return;
+      LOG(ERROR) << "This HTTP answer has not sent the proper number of bytes in its body";
+    }
+  }
+
+
+  void HttpOutput::StateMachine::SetHttpStatus(HttpStatus status)
+  {
+    if (state_ != State_WritingHeader)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
 
-    stream_.OnHttpStatusReceived(status);
-    state_ = State_WritingHeader;
+    status_ = status;
+  }
+
+
+  void HttpOutput::StateMachine::SetContentLength(uint64_t length)
+  {
+    if (state_ != State_WritingHeader)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
 
-    std::string s = "HTTP/1.1 " + 
-      boost::lexical_cast<std::string>(status) +
-      " " + std::string(EnumerationToString(status)) +
-      "\r\n";
+    hasContentLength_ = true;
+    contentLength_ = length;
+  }
 
-    stream_.Send(true, &s[0], s.size());
+  void HttpOutput::StateMachine::SetContentType(const char* contentType)
+  {
+    AddHeader("Content-Type", contentType);
+  }
+
+  void HttpOutput::StateMachine::SetContentFilename(const char* filename)
+  {
+    // TODO Escape double quotes
+    AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\"");
   }
 
-  void HttpOutput::StateMachine::SendHeaderData(const void* buffer, size_t length)
+  void HttpOutput::StateMachine::SetCookie(const std::string& cookie,
+                                           const std::string& value)
+  {
+    if (state_ != State_WritingHeader)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    // TODO Escape "=" characters
+    AddHeader("Set-Cookie", cookie + "=" + value);
+  }
+
+
+  void HttpOutput::StateMachine::AddHeader(const std::string& header,
+                                           const std::string& value)
+  {
+    if (state_ != State_WritingHeader)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    headers_.push_back(header + ": " + value + "\r\n");
+  }
+
+  void HttpOutput::StateMachine::ClearHeaders()
   {
     if (state_ != State_WritingHeader)
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
 
-    stream_.Send(true, buffer, length);
+    headers_.clear();
   }
 
-  void HttpOutput::StateMachine::SendHeaderString(const std::string& str)
+  void HttpOutput::StateMachine::SendBody(const void* buffer, size_t length)
   {
-    if (str.size() > 0)
+    if (state_ == State_Done)
     {
-      SendHeaderData(&str[0], str.size());
-    }
-  }
-
-  void HttpOutput::StateMachine::SendBodyData(const void* buffer, size_t length)
-  {
-    if (state_ == State_WaitingHttpStatus)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      if (length == 0)
+      {
+        return;
+      }
+      else
+      {
+        LOG(ERROR) << "Because of keep-alive connections, the entire body must be sent at once or Content-Length must be given";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
     }
 
     if (state_ == State_WritingHeader)
     {
-      // Close the HTTP header before writing the body
-      stream_.Send(true, "\r\n", 2);
+      // Send the HTTP header before writing the body
+
+      stream_.OnHttpStatusReceived(status_);
+
+      std::string s = "HTTP/1.1 " + 
+        boost::lexical_cast<std::string>(status_) +
+        " " + std::string(EnumerationToString(status_)) +
+        "\r\n";
+
+      if (status_ != HttpStatus_200_Ok)
+      {
+        hasContentLength_ = false;
+      }
+
+      for (std::list<std::string>::const_iterator
+             it = headers_.begin(); it != headers_.end(); ++it)
+      {
+        s += *it;
+      }
+
+      uint64_t contentLength = (hasContentLength_ ? contentLength_ : length);
+      s += "Content-Length: " + boost::lexical_cast<std::string>(contentLength) + "\r\n\r\n";
+
+      stream_.Send(true, s.c_str(), s.size());
       state_ = State_WritingBody;
     }
 
+    if (hasContentLength_ &&
+        contentPosition_ + length > contentLength_)
+    {
+      LOG(ERROR) << "The body size exceeds what was declared with SetContentSize()";
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
     if (length > 0)
     {
       stream_.Send(false, buffer, length);
-    }
-  }
-
-  void HttpOutput::StateMachine::SendBodyString(const std::string& str)
-  {
-    if (str.size() > 0)
-    {
-      SendBodyData(&str[0], str.size());
-    }
-  }
-
-
-  void HttpOutput::PrepareOkHeader(Header& header,
-                                   const char* contentType,
-                                   bool hasContentLength,
-                                   uint64_t contentLength,
-                                   const char* contentFilename)
-  {
-    header.clear();
-
-    if (contentType && contentType[0] != '\0')
-    {
-      header.push_back(std::make_pair("Content-Type", std::string(contentType)));
-    }
-
-    if (hasContentLength)
-    {
-      header.push_back(std::make_pair("Content-Length", boost::lexical_cast<std::string>(contentLength)));
+      contentPosition_ += length;
     }
 
-    if (contentFilename && contentFilename[0] != '\0')
+    if (!hasContentLength_ ||
+        contentPosition_ == contentLength_)
     {
-      std::string attachment = "attachment; filename=\"" + std::string(contentFilename) + "\"";
-      header.push_back(std::make_pair("Content-Disposition", attachment));
+      state_ = State_Done;
     }
   }
 
-  void HttpOutput::SendOkHeader(const char* contentType,
-                                bool hasContentLength,
-                                uint64_t contentLength,
-                                const char* contentFilename)
-  {
-    Header header;
-    PrepareOkHeader(header, contentType, hasContentLength, contentLength, contentFilename);
-    SendOkHeader(header);
-  }
-
-  void HttpOutput::SendOkHeader(const Header& header)
-  {
-    stateMachine_.SendHttpStatus(HttpStatus_200_Ok);
-
-    std::string s;
-    for (Header::const_iterator 
-           it = header.begin(); it != header.end(); ++it)
-    {
-      s += it->first + ": " + it->second + "\r\n";
-    }
-
-    for (HttpHandler::Arguments::const_iterator 
-           it = cookies_.begin(); it != cookies_.end(); ++it)
-    {
-      s += "Set-Cookie: " + it->first + "=" + it->second + "\r\n";
-    }
-
-    stateMachine_.SendHeaderString(s);
-  }
-
 
   void HttpOutput::SendMethodNotAllowed(const std::string& allowed)
   {
-    stateMachine_.SendHttpStatus(HttpStatus_405_MethodNotAllowed);
-    stateMachine_.SendHeaderString("Allow: " + allowed + "\r\n");
+    stateMachine_.ClearHeaders();
+    stateMachine_.SetHttpStatus(HttpStatus_405_MethodNotAllowed);
+    stateMachine_.AddHeader("Allow", allowed);
+    stateMachine_.SendBody(NULL, 0);
   }
 
 
-  void HttpOutput::SendHeader(HttpStatus status)
+  void HttpOutput::SendStatus(HttpStatus status)
   {
     if (status == HttpStatus_200_Ok ||
         status == HttpStatus_301_MovedPermanently ||
         status == HttpStatus_401_Unauthorized ||
         status == HttpStatus_405_MethodNotAllowed)
     {
-      throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput");
+      LOG(ERROR) << "Please use the dedicated methods to this HTTP status code in HttpOutput";
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
     
-    stateMachine_.SendHttpStatus(status);
-  }
-
-
-  void HttpOutput::AnswerBufferWithContentType(const std::string& buffer,
-                                               const std::string& contentType)
-  {
-    Header header;
-    PrepareOkHeader(header, contentType.c_str(), true, buffer.size(), NULL);
-    SendOkHeader(header);
-    SendBodyString(buffer);
-  }
-
-
-  void HttpOutput::AnswerBufferWithContentType(const void* buffer,
-                                               size_t size,
-                                               const std::string& contentType)
-  {
-    Header header;
-    PrepareOkHeader(header, contentType.c_str(), true, size, NULL);
-    SendOkHeader(header);
-    SendBodyData(buffer, size);
+    stateMachine_.ClearHeaders();
+    stateMachine_.SetHttpStatus(status);
+    stateMachine_.SendBody(NULL, 0);
   }
 
 
   void HttpOutput::Redirect(const std::string& path)
   {
-    stateMachine_.SendHttpStatus(HttpStatus_301_MovedPermanently);
-    stateMachine_.SendHeaderString("Location: " + path + "\r\n");
+    stateMachine_.ClearHeaders();
+    stateMachine_.SetHttpStatus(HttpStatus_301_MovedPermanently);
+    stateMachine_.AddHeader("Location", path);
+    stateMachine_.SendBody(NULL, 0);
   }
 
 
   void HttpOutput::SendUnauthorized(const std::string& realm)
   {
-    stateMachine_.SendHttpStatus(HttpStatus_401_Unauthorized);
-    stateMachine_.SendHeaderString("WWW-Authenticate: Basic realm=\"" + realm + "\"\r\n");
+    stateMachine_.ClearHeaders();
+    stateMachine_.SetHttpStatus(HttpStatus_401_Unauthorized);
+    stateMachine_.AddHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
+    stateMachine_.SendBody(NULL, 0);
+  }
+
+  void HttpOutput::SendBody(const void* buffer, size_t length)
+  {
+    stateMachine_.SendBody(buffer, length);
   }
 
+  void HttpOutput::SendBody(const std::string& str)
+  {
+    if (str.size() == 0)
+    {
+      stateMachine_.SendBody(NULL, 0);
+    }
+    else
+    {
+      stateMachine_.SendBody(str.c_str(), str.size());
+    }
+  }
+
+  void HttpOutput::SendBody()
+  {
+    stateMachine_.SendBody(NULL, 0);
+  }
 }
--- a/Core/HttpServer/HttpOutput.h	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/HttpOutput.h	Tue Sep 02 15:51:20 2014 +0200
@@ -51,84 +51,84 @@
     private:
       enum State
       {
-        State_WaitingHttpStatus,
         State_WritingHeader,      
-        State_WritingBody
+        State_WritingBody,
+        State_Done
       };
 
       IHttpOutputStream& stream_;
       State state_;
 
+      HttpStatus status_;
+      bool hasContentLength_;
+      uint64_t contentLength_;
+      uint64_t contentPosition_;
+      std::list<std::string> headers_;
+
     public:
-      StateMachine(IHttpOutputStream& stream) : 
-        stream_(stream),
-        state_(State_WaitingHttpStatus)
-      {
-      }
+      StateMachine(IHttpOutputStream& stream);
+
+      ~StateMachine();
+
+      void SetHttpStatus(HttpStatus status);
 
-      void SendHttpStatus(HttpStatus status);
+      void SetContentLength(uint64_t length);
 
-      void SendHeaderData(const void* buffer, size_t length);
+      void SetContentType(const char* contentType);
+
+      void SetContentFilename(const char* filename);
 
-      void SendHeaderString(const std::string& str);
+      void SetCookie(const std::string& cookie,
+                     const std::string& value);
 
-      void SendBodyData(const void* buffer, size_t length);
+      void AddHeader(const std::string& header,
+                     const std::string& value);
 
-      void SendBodyString(const std::string& str);
+      void ClearHeaders();
+
+      void SendBody(const void* buffer, size_t length);
     };
 
-    void PrepareOkHeader(Header& header,
-                         const char* contentType,
-                         bool hasContentLength,
-                         uint64_t contentLength,
-                         const char* contentFilename);
-
-    void SendOkHeader(const Header& header);
-
     StateMachine stateMachine_;
-    HttpHandler::Arguments cookies_;
 
   public:
     HttpOutput(IHttpOutputStream& stream) : stateMachine_(stream)
     {
     }
 
-    void SendOkHeader(const char* contentType,
-                      bool hasContentLength,
-                      uint64_t contentLength,
-                      const char* contentFilename);
+    void SendStatus(HttpStatus status);
 
-    void SendBodyData(const void* buffer, size_t length)
+    void SetContentType(const char* contentType)
     {
-      stateMachine_.SendBodyData(buffer, length);
+      stateMachine_.SetContentType(contentType);
+    }
+
+    void SetContentFilename(const char* filename)
+    {
+      stateMachine_.SetContentFilename(filename);
     }
 
-    void SendBodyString(const std::string& str)
+    void SetContentLength(uint64_t length)
     {
-      stateMachine_.SendBodyString(str);
+      stateMachine_.SetContentLength(length);
     }
 
-    void SendMethodNotAllowed(const std::string& allowed);
+    void SetCookie(const std::string& cookie,
+                   const std::string& value)
+    {
+      stateMachine_.SetCookie(cookie, value);
+    }
 
-    void SendHeader(HttpStatus status);
+    void SendBody(const void* buffer, size_t length);
+
+    void SendBody(const std::string& str);
+
+    void SendBody();
+
+    void SendMethodNotAllowed(const std::string& allowed);
 
     void Redirect(const std::string& path);
 
     void SendUnauthorized(const std::string& realm);
-
-    void SetCookie(const std::string& cookie,
-                   const std::string& value)
-    {
-      cookies_[cookie] = value;
-    }
-
-    // Higher-level constructs to send entire buffers ----------------------------
-
-    void AnswerBufferWithContentType(const std::string& buffer,
-                                     const std::string& contentType);
-
-    void AnswerBufferWithContentType(const void* buffer,
-                                     size_t size,
-                                     const std::string& contentType);
   };
 }
--- a/Core/HttpServer/MongooseServer.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -585,7 +585,7 @@
     HttpMethod method;
     if (!ExtractMethod(method, request, headers, argumentsGET))
     {
-      output.SendHeader(HttpStatus_400_BadRequest);
+      output.SendStatus(HttpStatus_400_BadRequest);
       return;
     }
 
@@ -594,6 +594,7 @@
     if (that->IsAuthenticationEnabled() &&
         !Authorize(*that, headers, output))
     {
+      output.SendUnauthorized(ORTHANC_REALM);
       return;
     }
 
@@ -649,15 +650,15 @@
       switch (status)
       {
         case PostDataStatus_NoLength:
-          output.SendHeader(HttpStatus_411_LengthRequired);
+          output.SendStatus(HttpStatus_411_LengthRequired);
           return;
 
         case PostDataStatus_Failure:
-          output.SendHeader(HttpStatus_400_BadRequest);
+          output.SendStatus(HttpStatus_400_BadRequest);
           return;
 
         case PostDataStatus_Pending:
-          output.AnswerBufferWithContentType(NULL, 0, "");
+          output.SendBody();
           return;
 
         default:
@@ -674,7 +675,7 @@
     }
     catch (OrthancException)
     {
-      output.SendHeader(HttpStatus_400_BadRequest);
+      output.SendStatus(HttpStatus_400_BadRequest);
       return;
     }
 
@@ -694,29 +695,43 @@
       {
         // Using this candidate handler results in an exception
         LOG(ERROR) << "Exception in the HTTP handler: " << e.What();
+
+        switch (e.GetErrorCode())
+        {
+          case ErrorCode_InexistentFile:
+          case ErrorCode_InexistentItem:
+          case ErrorCode_UnknownResource:
+            output.SendStatus(HttpStatus_404_NotFound);
+            break;
+
+          case ErrorCode_BadRequest:
+          case ErrorCode_UriSyntax:
+            output.SendStatus(HttpStatus_400_BadRequest);
+            break;
+
+          default:
+            output.SendStatus(HttpStatus_500_InternalServerError);
+        }
+
         return;
       }
       catch (boost::bad_lexical_cast&)
       {
         LOG(ERROR) << "Exception in the HTTP handler: Bad lexical cast";
+        output.SendStatus(HttpStatus_400_BadRequest);
         return;
       }
       catch (std::runtime_error&)
       {
         LOG(ERROR) << "Exception in the HTTP handler: Presumably a bad JSON request";
+        output.SendStatus(HttpStatus_400_BadRequest);
         return;
       }
     }
 
     if (!found)
     {
-      try
-      {
-        output.SendHeader(HttpStatus_404_NotFound);
-      }
-      catch (OrthancException&)
-      {
-      }
+      output.SendStatus(HttpStatus_404_NotFound);
     }
   }
 
--- a/Core/RestApi/RestApi.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/RestApi/RestApi.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -192,6 +192,7 @@
 
     if (root_.LookupResource(uri, visitor))
     {
+      wrappedOutput.Finalize();
       return true;
     }
 
--- a/Core/RestApi/RestApiOutput.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/RestApi/RestApiOutput.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -50,6 +50,14 @@
   RestApiOutput::~RestApiOutput()
   {
   }
+
+  void RestApiOutput::Finalize()
+  {
+    if (!alreadySent_)
+    {
+      output_.SendStatus(HttpStatus_404_NotFound);
+    }
+  }
   
   void RestApiOutput::CheckStatus()
   {
@@ -75,7 +83,8 @@
 #if ORTHANC_PUGIXML_ENABLED == 1
       std::string s;
       Toolbox::JsonToXml(s, value);
-      output_.AnswerBufferWithContentType(s, "application/xml");
+      output_.SetContentType("application/xml");
+      output_.SendBody(s);
 #else
       LOG(ERROR) << "Orthanc was compiled without XML support";
       throw OrthancException(ErrorCode_InternalError);
@@ -84,8 +93,8 @@
     else
     {
       Json::StyledWriter writer;
-      std::string s = writer.write(value);
-      output_.AnswerBufferWithContentType(s, "application/json");
+      output_.SetContentType("application/json");
+      output_.SendBody(writer.write(value));
     }
 
     alreadySent_ = true;
@@ -95,7 +104,8 @@
                                    const std::string& contentType)
   {
     CheckStatus();
-    output_.AnswerBufferWithContentType(buffer, contentType);
+    output_.SetContentType(contentType.c_str());
+    output_.SendBody(buffer);
     alreadySent_ = true;
   }
 
@@ -104,7 +114,8 @@
                                    const std::string& contentType)
   {
     CheckStatus();
-    output_.AnswerBufferWithContentType(buffer, length, contentType);
+    output_.SetContentType(contentType.c_str());
+    output_.SendBody(buffer, length);
     alreadySent_ = true;
   }
 
@@ -124,7 +135,7 @@
     }
 
     CheckStatus();
-    output_.SendHeader(status);
+    output_.SendStatus(status);
     alreadySent_ = true;    
   }
 
--- a/Core/RestApi/RestApiOutput.h	Mon Sep 01 12:20:26 2014 +0200
+++ b/Core/RestApi/RestApiOutput.h	Tue Sep 02 15:51:20 2014 +0200
@@ -93,5 +93,7 @@
                    unsigned int maxAge = 0);
 
     void ResetCookie(const std::string& name);
+
+    void Finalize();
   };
 }
--- a/NEWS	Mon Sep 01 12:20:26 2014 +0200
+++ b/NEWS	Tue Sep 02 15:51:20 2014 +0200
@@ -1,7 +1,7 @@
 Pending changes in the mainline
 ===============================
 
-* Upgrade to Mongoose 3.8 (the last release under the MIT license)
+* Refactoring of HttpOutput ("Content-Length" header is now always sent)
 * Fixes for Visual Studio 2013 and Visual Studio 64bit
 
 
--- a/OrthancServer/ParsedDicomFile.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -264,7 +264,8 @@
     Uint32 length = element.getLength(transferSyntax);
     Uint32 offset = 0;
 
-    output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL);
+    output.GetLowLevelOutput().SetContentType(CONTENT_TYPE_OCTET_STREAM);
+    output.GetLowLevelOutput().SetContentLength(length);
 
     while (offset < length)
     {
@@ -282,7 +283,7 @@
 
       if (cond.good())
       {
-        output.GetLowLevelOutput().SendBodyData(&buffer[0], nbytes);
+        output.GetLowLevelOutput().SendBody(&buffer[0], nbytes);
         offset += nbytes;
       }
       else
--- a/OrthancServer/ServerIndex.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/OrthancServer/ServerIndex.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -866,7 +866,7 @@
     ResourceType type;
     if (!db_->LookupResource(instanceUuid, id, type))
     {
-      throw OrthancException(ErrorCode_InternalError);
+      throw OrthancException(ErrorCode_UnknownResource);
     }
 
     if (db_->LookupAttachment(attachment, id, contentType))
--- a/Plugins/Engine/PluginsHttpHandler.cpp	Mon Sep 01 12:20:26 2014 +0200
+++ b/Plugins/Engine/PluginsHttpHandler.cpp	Tue Sep 02 15:51:20 2014 +0200
@@ -353,7 +353,8 @@
       *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
 
     HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->AnswerBufferWithContentType(p.answer, p.answerSize, p.mimeType);
+    translatedOutput->SetContentType(p.mimeType);
+    translatedOutput->SendBody(p.answer, p.answerSize);
   }
 
 
@@ -373,7 +374,7 @@
       *reinterpret_cast<const _OrthancPluginSendHttpStatusCode*>(parameters);
 
     HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->SendHeader(static_cast<HttpStatus>(p.status));
+    translatedOutput->SendStatus(static_cast<HttpStatus>(p.status));
   }
 
 
@@ -448,7 +449,8 @@
     std::string png;
     writer.WriteToMemory(png, accessor);
 
-    translatedOutput->AnswerBufferWithContentType(png, "image/png");
+    translatedOutput->SetContentType("image/png");
+    translatedOutput->SendBody(png);
   }