Mercurial > hg > orthanc
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); + } }