diff Core/HttpServer/HttpOutput.cpp @ 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 8d1845feb277
children da56a7916e8a
line wrap: on
line diff
--- 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);
+  }
 }