# HG changeset patch # User Sebastien Jodogne # Date 1547746252 -3600 # Node ID af4fab776ff22859cf6a9eb7ca42604937ff7bfc # Parent 57478347f8463ff49106cb2e2aaa3fead6fca8a1# Parent ab46e537f92e12b9bbef535b806ff7ee898b3021 integration mainline->db-changes diff -r 57478347f846 -r af4fab776ff2 Core/HttpServer/HttpServer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpServer.cpp Thu Jan 17 18:30:52 2019 +0100 @@ -0,0 +1,1184 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +// http://en.highscore.de/cpp/boost/stringhandling.html + +#include "../PrecompiledHeaders.h" +#include "HttpServer.h" + +#include "../Logging.h" +#include "../ChunkedBuffer.h" +#include "../OrthancException.h" +#include "HttpToolbox.h" + +#if ORTHANC_ENABLE_MONGOOSE == 1 +# include + +#elif ORTHANC_ENABLE_CIVETWEB == 1 +# include +# define MONGOOSE_USE_CALLBACKS 1 + +#else +# error "Either Mongoose or Civetweb must be enabled to compile this file" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(ORTHANC_ENABLE_SSL) +# error The macro ORTHANC_ENABLE_SSL must be defined +#endif + +#if ORTHANC_ENABLE_SSL == 1 +#include +#endif + +#define ORTHANC_REALM "Orthanc Secure Area" + + +namespace Orthanc +{ + static const char multipart[] = "multipart/form-data; boundary="; + static unsigned int multipartLength = sizeof(multipart) / sizeof(char) - 1; + + + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + class MongooseOutputStream : public IHttpOutputStream + { + private: + struct mg_connection* connection_; + + public: + MongooseOutputStream(struct mg_connection* connection) : connection_(connection) + { + } + + virtual void Send(bool isHeader, const void* buffer, size_t length) + { + if (length > 0) + { + int status = mg_write(connection_, buffer, length); + if (status != static_cast(length)) + { + // status == 0 when the connection has been closed, -1 on error + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + } + + virtual void OnHttpStatusReceived(HttpStatus status) + { + // Ignore this + } + }; + + + enum PostDataStatus + { + PostDataStatus_Success, + PostDataStatus_NoLength, + PostDataStatus_Pending, + PostDataStatus_Failure + }; + } + + +// TODO Move this to external file + + + class ChunkedFile : public ChunkedBuffer + { + private: + std::string filename_; + + public: + ChunkedFile(const std::string& filename) : + filename_(filename) + { + } + + const std::string& GetFilename() const + { + return filename_; + } + }; + + + + class ChunkStore + { + private: + typedef std::list Content; + Content content_; + unsigned int numPlaces_; + + boost::mutex mutex_; + std::set discardedFiles_; + + void Clear() + { + for (Content::iterator it = content_.begin(); + it != content_.end(); ++it) + { + delete *it; + } + } + + Content::iterator Find(const std::string& filename) + { + for (Content::iterator it = content_.begin(); + it != content_.end(); ++it) + { + if ((*it)->GetFilename() == filename) + { + return it; + } + } + + return content_.end(); + } + + void Remove(const std::string& filename) + { + Content::iterator it = Find(filename); + if (it != content_.end()) + { + delete *it; + content_.erase(it); + } + } + + public: + ChunkStore() + { + numPlaces_ = 10; + } + + ~ChunkStore() + { + Clear(); + } + + PostDataStatus Store(std::string& completed, + const char* chunkData, + size_t chunkSize, + const std::string& filename, + size_t filesize) + { + boost::mutex::scoped_lock lock(mutex_); + + std::set::iterator wasDiscarded = discardedFiles_.find(filename); + if (wasDiscarded != discardedFiles_.end()) + { + discardedFiles_.erase(wasDiscarded); + return PostDataStatus_Failure; + } + + ChunkedFile* f; + Content::iterator it = Find(filename); + if (it == content_.end()) + { + f = new ChunkedFile(filename); + + // Make some room + if (content_.size() >= numPlaces_) + { + discardedFiles_.insert(content_.front()->GetFilename()); + delete content_.front(); + content_.pop_front(); + } + + content_.push_back(f); + } + else + { + f = *it; + } + + f->AddChunk(chunkData, chunkSize); + + if (f->GetNumBytes() > filesize) + { + Remove(filename); + } + else if (f->GetNumBytes() == filesize) + { + f->Flatten(completed); + Remove(filename); + return PostDataStatus_Success; + } + + return PostDataStatus_Pending; + } + + /*void Print() + { + boost::mutex::scoped_lock lock(mutex_); + + printf("ChunkStore status:\n"); + for (Content::const_iterator i = content_.begin(); + i != content_.end(); i++) + { + printf(" [%s]: %d\n", (*i)->GetFilename().c_str(), (*i)->GetNumBytes()); + } + printf("-----\n"); + }*/ + }; + + + struct HttpServer::PImpl + { + struct mg_context *context_; + ChunkStore chunkStore_; + }; + + + ChunkStore& HttpServer::GetChunkStore() + { + return pimpl_->chunkStore_; + } + + + + static PostDataStatus ReadBody(std::string& postData, + struct mg_connection *connection, + const IHttpHandler::Arguments& headers) + { + IHttpHandler::Arguments::const_iterator cs = headers.find("content-length"); + if (cs == headers.end()) + { + return PostDataStatus_NoLength; + } + + int length; + try + { + length = boost::lexical_cast(cs->second); + } + catch (boost::bad_lexical_cast&) + { + 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) + { + return PostDataStatus_Failure; + } + + assert(r <= length); + length -= r; + pos += r; + } + + return PostDataStatus_Success; + } + + + + static PostDataStatus ParseMultipartPost(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 postData; + PostDataStatus status = ReadBody(postData, connection, headers); + + if (status != PostDataStatus_Success) + { + return status; + } + + /*for (IHttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++) + { + std::cout << "Header [" << i->first << "] = " << i->second << "\n"; + } + printf("CHUNK\n");*/ + + typedef IHttpHandler::Arguments::const_iterator ArgumentIterator; + + ArgumentIterator requestedWith = headers.find("x-requested-with"); + ArgumentIterator fileName = headers.find("x-file-name"); + ArgumentIterator fileSizeStr = headers.find("x-file-size"); + + if (requestedWith != headers.end() && + requestedWith->second != "XMLHttpRequest") + { + return PostDataStatus_Failure; + } + + size_t fileSize = 0; + if (fileSizeStr != headers.end()) + { + try + { + fileSize = boost::lexical_cast(fileSizeStr->second); + } + catch (boost::bad_lexical_cast&) + { + return PostDataStatus_Failure; + } + } + + typedef boost::find_iterator FindIterator; + typedef boost::iterator_range Range; + + //chunkStore.Print(); + + try + { + FindIterator last; + for (FindIterator it = + make_find_iterator(postData, boost::first_finder(boundary)); + it!=FindIterator(); + ++it) + { + if (last != FindIterator()) + { + Range part(&last->back(), &it->front()); + Range content = boost::find_first(part, "\r\n\r\n"); + if (/*content != Range()*/!content.empty()) + { + Range c(&content.back() + 1, &it->front() - 2); + size_t chunkSize = c.size(); + + if (chunkSize > 0) + { + const char* chunkData = &c.front(); + + if (fileName == headers.end()) + { + // This file is stored in a single chunk + completedFile.resize(chunkSize); + if (chunkSize > 0) + { + memcpy(&completedFile[0], chunkData, chunkSize); + } + return PostDataStatus_Success; + } + else + { + return chunkStore.Store(completedFile, chunkData, chunkSize, fileName->second, fileSize); + } + } + } + } + + last = it; + } + } + catch (std::length_error&) + { + return PostDataStatus_Failure; + } + + return PostDataStatus_Pending; + } + + + static bool IsAccessGranted(const HttpServer& that, + const IHttpHandler::Arguments& headers) + { + bool granted = false; + + IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); + if (auth != headers.end()) + { + std::string s = auth->second; + if (s.size() > 6 && + s.substr(0, 6) == "Basic ") + { + std::string b64 = s.substr(6); + granted = that.IsValidBasicHttpAuthentication(b64); + } + } + + return granted; + } + + + static std::string GetAuthenticatedUsername(const IHttpHandler::Arguments& headers) + { + IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); + + if (auth == headers.end()) + { + return ""; + } + + std::string s = auth->second; + if (s.size() <= 6 || + s.substr(0, 6) != "Basic ") + { + return ""; + } + + std::string b64 = s.substr(6); + std::string decoded; + Toolbox::DecodeBase64(decoded, b64); + size_t semicolons = decoded.find(':'); + + if (semicolons == std::string::npos) + { + // Bad-formatted request + return ""; + } + else + { + return decoded.substr(0, semicolons); + } + } + + + static bool ExtractMethod(HttpMethod& method, + const struct mg_request_info *request, + const IHttpHandler::Arguments& headers, + const IHttpHandler::GetArguments& argumentsGET) + { + std::string overriden; + + // Check whether some PUT/DELETE faking is done + + // 1. Faking with Google's approach + IHttpHandler::Arguments::const_iterator methodOverride = + headers.find("x-http-method-override"); + + if (methodOverride != headers.end()) + { + overriden = methodOverride->second; + } + else if (!strcmp(request->request_method, "GET")) + { + // 2. Faking with Ruby on Rail's approach + // GET /my/resource?_method=delete <=> DELETE /my/resource + for (size_t i = 0; i < argumentsGET.size(); i++) + { + if (argumentsGET[i].first == "_method") + { + overriden = argumentsGET[i].second; + break; + } + } + } + + if (overriden.size() > 0) + { + // A faking has been done within this request + Toolbox::ToUpperCase(overriden); + + LOG(INFO) << "HTTP method faking has been detected for " << overriden; + + if (overriden == "PUT") + { + method = HttpMethod_Put; + return true; + } + else if (overriden == "DELETE") + { + method = HttpMethod_Delete; + return true; + } + else + { + return false; + } + } + + // No PUT/DELETE faking was present + if (!strcmp(request->request_method, "GET")) + { + method = HttpMethod_Get; + } + else if (!strcmp(request->request_method, "POST")) + { + method = HttpMethod_Post; + } + else if (!strcmp(request->request_method, "DELETE")) + { + method = HttpMethod_Delete; + } + else if (!strcmp(request->request_method, "PUT")) + { + method = HttpMethod_Put; + } + else + { + return false; + } + + return true; + } + + + static void ConfigureHttpCompression(HttpOutput& output, + const IHttpHandler::Arguments& headers) + { + // Look if the client wishes HTTP compression + // https://en.wikipedia.org/wiki/HTTP_compression + IHttpHandler::Arguments::const_iterator it = headers.find("accept-encoding"); + if (it != headers.end()) + { + std::vector encodings; + Toolbox::TokenizeString(encodings, it->second, ','); + + for (size_t i = 0; i < encodings.size(); i++) + { + std::string s = Toolbox::StripSpaces(encodings[i]); + + if (s == "deflate") + { + output.SetDeflateAllowed(true); + } + else if (s == "gzip") + { + output.SetGzipAllowed(true); + } + } + } + } + + + static void InternalCallback(HttpOutput& output /* out */, + HttpMethod& method /* out */, + HttpServer& server, + struct mg_connection *connection, + const struct mg_request_info *request) + { + bool localhost; + +#if ORTHANC_ENABLE_MONGOOSE == 1 + static const long LOCALHOST = (127ll << 24) + 1ll; + localhost = (request->remote_ip == LOCALHOST); +#elif ORTHANC_ENABLE_CIVETWEB == 1 + // The "remote_ip" field of "struct mg_request_info" is tagged as + // deprecated in Civetweb, using "remote_addr" instead. + localhost = (std::string(request->remote_addr) == "127.0.0.1"); +#else +# error +#endif + + // Check remote calls + if (!server.IsRemoteAccessAllowed() && + !localhost) + { + output.SendUnauthorized(server.GetRealm()); + return; + } + + + // Extract the HTTP headers + IHttpHandler::Arguments headers; + for (int i = 0; i < request->num_headers; i++) + { + std::string name = request->http_headers[i].name; + std::string value = request->http_headers[i].value; + + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + headers.insert(std::make_pair(name, value)); + VLOG(1) << "HTTP header: [" << name << "]: [" << value << "]"; + } + + if (server.IsHttpCompressionEnabled()) + { + ConfigureHttpCompression(output, headers); + } + + + // Extract the GET arguments + IHttpHandler::GetArguments argumentsGET; + if (!strcmp(request->request_method, "GET")) + { + HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); + } + + + // Compute the HTTP method, taking method faking into consideration + method = HttpMethod_Get; + if (!ExtractMethod(method, request, headers, argumentsGET)) + { + output.SendStatus(HttpStatus_400_BadRequest); + return; + } + + + // Authenticate this connection + if (server.IsAuthenticationEnabled() && + !IsAccessGranted(server, headers)) + { + output.SendUnauthorized(server.GetRealm()); + return; + } + + +#if ORTHANC_ENABLE_MONGOOSE == 1 + // Apply the filter, if it is installed + char remoteIp[24]; + sprintf(remoteIp, "%d.%d.%d.%d", + reinterpret_cast(&request->remote_ip) [3], + reinterpret_cast(&request->remote_ip) [2], + reinterpret_cast(&request->remote_ip) [1], + reinterpret_cast(&request->remote_ip) [0]); + + const char* requestUri = request->uri; + +#elif ORTHANC_ENABLE_CIVETWEB == 1 + const char* remoteIp = request->remote_addr; + const char* requestUri = request->local_uri; +#else +# error +#endif + + if (requestUri == NULL) + { + requestUri = ""; + } + + std::string username = GetAuthenticatedUsername(headers); + + IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); + if (filter != NULL) + { + if (!filter->IsAllowed(method, requestUri, remoteIp, + username.c_str(), headers, argumentsGET)) + { + //output.SendUnauthorized(server.GetRealm()); + output.SendStatus(HttpStatus_403_Forbidden); + return; + } + } + + + // Extract the body of the request for PUT and POST + + // TODO Avoid unneccessary memcopy of the body + + std::string body; + if (method == HttpMethod_Post || + method == HttpMethod_Put) + { + PostDataStatus status; + + IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); + if (ct == headers.end()) + { + // No content-type specified. Assume no multi-part content occurs at this point. + status = ReadBody(body, connection, headers); + } + else + { + std::string contentType = ct->second; + if (contentType.size() >= multipartLength && + !memcmp(contentType.c_str(), multipart, multipartLength)) + { + status = ParseMultipartPost(body, connection, headers, contentType, server.GetChunkStore()); + } + else + { + status = ReadBody(body, connection, headers); + } + } + + switch (status) + { + case PostDataStatus_NoLength: + output.SendStatus(HttpStatus_411_LengthRequired); + return; + + case PostDataStatus_Failure: + output.SendStatus(HttpStatus_400_BadRequest); + return; + + case PostDataStatus_Pending: + output.AnswerEmpty(); + return; + + default: + break; + } + } + + + // 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()) + { + found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), + method, uri, headers, argumentsGET, body.c_str(), body.size()); + } + + if (!found) + { + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + static void ProtectedCallback(struct mg_connection *connection, + const struct mg_request_info *request) + { + try + { +#if ORTHANC_ENABLE_MONGOOSE == 1 + void *that = request->user_data; + const char* requestUri = request->uri; +#elif ORTHANC_ENABLE_CIVETWEB == 1 + // https://github.com/civetweb/civetweb/issues/409 + void *that = mg_get_user_data(mg_get_context(connection)); + const char* requestUri = request->local_uri; +#else +# error +#endif + + if (requestUri == NULL) + { + requestUri = ""; + } + + HttpServer* server = reinterpret_cast(that); + + if (server == NULL) + { + MongooseOutputStream stream(connection); + HttpOutput output(stream, false /* assume no keep-alive */); + output.SendStatus(HttpStatus_500_InternalServerError); + return; + } + + MongooseOutputStream stream(connection); + HttpOutput output(stream, server->IsKeepAliveEnabled()); + HttpMethod method = HttpMethod_Get; + + try + { + try + { + InternalCallback(output, method, *server, connection, request); + } + catch (OrthancException&) + { + throw; // Pass the exception to the main handler below + } + // Now convert native exceptions as OrthancException + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_BadParameterType, + "Syntax error in some user-supplied data"); + } + catch (std::runtime_error&) + { + // Presumably an error while parsing the JSON body + throw OrthancException(ErrorCode_BadRequest); + } + catch (std::bad_alloc&) + { + throw OrthancException(ErrorCode_NotEnoughMemory, + "The server hosting Orthanc is running out of memory"); + } + catch (...) + { + throw OrthancException(ErrorCode_InternalError, + "An unhandled exception was generated inside the HTTP server"); + } + } + catch (OrthancException& e) + { + assert(server != NULL); + + // Using this candidate handler results in an exception + try + { + if (server->GetExceptionFormatter() == NULL) + { + LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); + output.SendStatus(e.GetHttpStatus()); + } + else + { + server->GetExceptionFormatter()->Format(output, e, method, requestUri); + } + } + catch (OrthancException&) + { + // An exception here reflects the fact that the status code + // was already set by the HTTP handler. + } + } + } + catch (...) + { + // We should never arrive at this point, where it is even impossible to send an answer + LOG(ERROR) << "Catastrophic error inside the HTTP server, giving up"; + } + } + + +#if MONGOOSE_USE_CALLBACKS == 0 + static void* Callback(enum mg_event event, + struct mg_connection *connection, + const struct mg_request_info *request) + { + if (event == MG_NEW_REQUEST) + { + ProtectedCallback(connection, request); + + // Mark as processed + return (void*) ""; + } + else + { + return NULL; + } + } + +#elif MONGOOSE_USE_CALLBACKS == 1 + static int Callback(struct mg_connection *connection) + { + const struct mg_request_info *request = mg_get_request_info(connection); + + ProtectedCallback(connection, request); + + return 1; // Do not let Mongoose handle the request by itself + } + +#else +# error Please set MONGOOSE_USE_CALLBACKS +#endif + + + + + + bool HttpServer::IsRunning() const + { + return (pimpl_->context_ != NULL); + } + + + HttpServer::HttpServer() : pimpl_(new PImpl) + { + pimpl_->context_ = NULL; + handler_ = NULL; + remoteAllowed_ = false; + authentication_ = false; + ssl_ = false; + port_ = 8000; + filter_ = NULL; + keepAlive_ = false; + httpCompression_ = true; + exceptionFormatter_ = NULL; + realm_ = ORTHANC_REALM; + threadsCount_ = 50; // Default value in mongoose + tcpNoDelay_ = true; + +#if ORTHANC_ENABLE_SSL == 1 + // Check for the Heartbleed exploit + // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug + if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ && + OPENSSL_VERSION_NUMBER >= 0x1000100fL /* openssl-1.0.1 */) + { + LOG(WARNING) << "This version of OpenSSL is vulnerable to the Heartbleed exploit"; + } +#endif + } + + + HttpServer::~HttpServer() + { + Stop(); + } + + + void HttpServer::SetPortNumber(uint16_t port) + { + Stop(); + port_ = port; + } + + void HttpServer::Start() + { +#if ORTHANC_ENABLE_MONGOOSE == 1 + LOG(INFO) << "Starting embedded Web server using Mongoose"; +#elif ORTHANC_ENABLE_CIVETWEB == 1 + LOG(INFO) << "Starting embedded Web server using Civetweb"; +#else +# error +#endif + + if (!IsRunning()) + { + std::string port = boost::lexical_cast(port_); + std::string numThreads = boost::lexical_cast(threadsCount_); + + if (ssl_) + { + port += "s"; + } + + const char *options[] = { + // Set the TCP port for the HTTP server + "listening_ports", port.c_str(), + + // Optimization reported by Chris Hafey + // https://groups.google.com/d/msg/orthanc-users/CKueKX0pJ9E/_UCbl8T-VjIJ + "enable_keep_alive", (keepAlive_ ? "yes" : "no"), + +#if ORTHANC_ENABLE_CIVETWEB == 1 + // https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md#enable_keep_alive-no + "keep_alive_timeout_ms", (keepAlive_ ? "500" : "0"), +#endif + +#if ORTHANC_ENABLE_CIVETWEB == 1 + // Disable TCP Nagle's algorithm to maximize speed (this + // option is not available in Mongoose). + // https://groups.google.com/d/topic/civetweb/35HBR9seFjU/discussion + // https://eklitzke.org/the-caveats-of-tcp-nodelay + "tcp_nodelay", (tcpNoDelay_ ? "1" : "0"), +#endif + + // Set the number of threads + "num_threads", numThreads.c_str(), + + // Set the SSL certificate, if any. This must be the last option. + ssl_ ? "ssl_certificate" : NULL, + certificate_.c_str(), + NULL + }; + +#if MONGOOSE_USE_CALLBACKS == 0 + pimpl_->context_ = mg_start(&Callback, this, options); + +#elif MONGOOSE_USE_CALLBACKS == 1 + struct mg_callbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.begin_request = Callback; + pimpl_->context_ = mg_start(&callbacks, this, options); + +#else +# error Please set MONGOOSE_USE_CALLBACKS +#endif + + if (!pimpl_->context_) + { + throw OrthancException(ErrorCode_HttpPortInUse); + } + + LOG(WARNING) << "HTTP server listening on port: " << GetPortNumber() + << " (HTTPS encryption is " + << (IsSslEnabled() ? "enabled" : "disabled") + << ", remote access is " + << (IsRemoteAccessAllowed() ? "" : "not ") + << "allowed)"; + } + } + + void HttpServer::Stop() + { + if (IsRunning()) + { + mg_stop(pimpl_->context_); + pimpl_->context_ = NULL; + } + } + + + void HttpServer::ClearUsers() + { + Stop(); + registeredUsers_.clear(); + } + + + void HttpServer::RegisterUser(const char* username, + const char* password) + { + Stop(); + + std::string tag = std::string(username) + ":" + std::string(password); + std::string encoded; + Toolbox::EncodeBase64(encoded, tag); + registeredUsers_.insert(encoded); + } + + void HttpServer::SetSslEnabled(bool enabled) + { + Stop(); + +#if ORTHANC_ENABLE_SSL == 0 + if (enabled) + { + throw OrthancException(ErrorCode_SslDisabled); + } + else + { + ssl_ = false; + } +#else + ssl_ = enabled; +#endif + } + + + void HttpServer::SetKeepAliveEnabled(bool enabled) + { + Stop(); + keepAlive_ = enabled; + LOG(INFO) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled"); + +#if ORTHANC_ENABLE_MONGOOSE == 1 + if (enabled) + { + LOG(WARNING) << "You should disable HTTP keep alive, as you are using Mongoose"; + } +#endif + } + + + void HttpServer::SetAuthenticationEnabled(bool enabled) + { + Stop(); + authentication_ = enabled; + } + + void HttpServer::SetSslCertificate(const char* path) + { + Stop(); + certificate_ = path; + } + + void HttpServer::SetRemoteAccessAllowed(bool allowed) + { + Stop(); + remoteAllowed_ = allowed; + } + + void HttpServer::SetHttpCompressionEnabled(bool enabled) + { + Stop(); + httpCompression_ = enabled; + LOG(WARNING) << "HTTP compression is " << (enabled ? "enabled" : "disabled"); + } + + void HttpServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter) + { + Stop(); + filter_ = &filter; + } + + + void HttpServer::SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter) + { + Stop(); + exceptionFormatter_ = &formatter; + } + + + bool HttpServer::IsValidBasicHttpAuthentication(const std::string& basic) const + { + return registeredUsers_.find(basic) != registeredUsers_.end(); + } + + + void HttpServer::Register(IHttpHandler& handler) + { + Stop(); + handler_ = &handler; + } + + + IHttpHandler& HttpServer::GetHandler() const + { + if (handler_ == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *handler_; + } + + + void HttpServer::SetThreadsCount(unsigned int threads) + { + if (threads <= 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + Stop(); + threadsCount_ = threads; + } + + + void HttpServer::SetTcpNoDelay(bool tcpNoDelay) + { + Stop(); + tcpNoDelay_ = tcpNoDelay; + LOG(INFO) << "TCP_NODELAY for the HTTP sockets is set to " + << (tcpNoDelay ? "true" : "false"); + } +} diff -r 57478347f846 -r af4fab776ff2 Core/HttpServer/HttpServer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpServer.h Thu Jan 17 18:30:52 2019 +0100 @@ -0,0 +1,219 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_MONGOOSE) +# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to include this file +#endif + +#if !defined(ORTHANC_ENABLE_CIVETWEB) +# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to include this file +#endif + +#if (ORTHANC_ENABLE_MONGOOSE == 0 && \ + ORTHANC_ENABLE_CIVETWEB == 0) +# error Either ORTHANC_ENABLE_MONGOOSE or ORTHANC_ENABLE_CIVETWEB must be set to 1 +#endif + + +#include "IIncomingHttpRequestFilter.h" + +#include +#include +#include +#include +#include + +namespace Orthanc +{ + class ChunkStore; + class OrthancException; + + class IHttpExceptionFormatter : public boost::noncopyable + { + public: + virtual ~IHttpExceptionFormatter() + { + } + + virtual void Format(HttpOutput& output, + const OrthancException& exception, + HttpMethod method, + const char* uri) = 0; + }; + + + class HttpServer + { + private: + // http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom + struct PImpl; + boost::shared_ptr pimpl_; + + IHttpHandler *handler_; + + typedef std::set RegisteredUsers; + RegisteredUsers registeredUsers_; + + bool remoteAllowed_; + bool authentication_; + bool ssl_; + std::string certificate_; + uint16_t port_; + IIncomingHttpRequestFilter* filter_; + bool keepAlive_; + bool httpCompression_; + IHttpExceptionFormatter* exceptionFormatter_; + std::string realm_; + unsigned int threadsCount_; + bool tcpNoDelay_; + + bool IsRunning() const; + + public: + HttpServer(); + + ~HttpServer(); + + void SetPortNumber(uint16_t port); + + uint16_t GetPortNumber() const + { + return port_; + } + + void Start(); + + void Stop(); + + void ClearUsers(); + + void RegisterUser(const char* username, + const char* password); + + bool IsAuthenticationEnabled() const + { + return authentication_; + } + + void SetAuthenticationEnabled(bool enabled); + + bool IsSslEnabled() const + { + return ssl_; + } + + void SetSslEnabled(bool enabled); + + bool IsKeepAliveEnabled() const + { + return keepAlive_; + } + + void SetKeepAliveEnabled(bool enabled); + + const std::string& GetSslCertificate() const + { + return certificate_; + } + + void SetSslCertificate(const char* path); + + bool IsRemoteAccessAllowed() const + { + return remoteAllowed_; + } + + void SetRemoteAccessAllowed(bool allowed); + + bool IsHttpCompressionEnabled() const + { + return httpCompression_;; + } + + void SetHttpCompressionEnabled(bool enabled); + + IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const + { + return filter_; + } + + void SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter); + + ChunkStore& GetChunkStore(); + + bool IsValidBasicHttpAuthentication(const std::string& basic) const; + + void Register(IHttpHandler& handler); + + bool HasHandler() const + { + return handler_ != NULL; + } + + IHttpHandler& GetHandler() const; + + void SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter); + + IHttpExceptionFormatter* GetExceptionFormatter() + { + return exceptionFormatter_; + } + + const std::string& GetRealm() const + { + return realm_; + } + + void SetRealm(const std::string& realm) + { + realm_ = realm; + } + + void SetThreadsCount(unsigned int threads); + + unsigned int GetThreadsCount() const + { + return threadsCount_; + } + + // New in Orthanc 1.5.2, not available for Mongoose + void SetTcpNoDelay(bool tcpNoDelay); + + bool IsTcpNoDelay() const + { + return tcpNoDelay_; + } + }; +} diff -r 57478347f846 -r af4fab776ff2 Core/HttpServer/MongooseServer.cpp --- a/Core/HttpServer/MongooseServer.cpp Wed Jan 16 15:54:16 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1159 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -// http://en.highscore.de/cpp/boost/stringhandling.html - -#include "../PrecompiledHeaders.h" -#include "MongooseServer.h" - -#include "../Logging.h" -#include "../ChunkedBuffer.h" -#include "../OrthancException.h" -#include "HttpToolbox.h" - -#if ORTHANC_ENABLE_MONGOOSE == 1 -# include - -#elif ORTHANC_ENABLE_CIVETWEB == 1 -# include -# define MONGOOSE_USE_CALLBACKS 1 - -#else -# error "Either Mongoose or Civetweb must be enabled to compile this file" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#if !defined(ORTHANC_ENABLE_SSL) -# error The macro ORTHANC_ENABLE_SSL must be defined -#endif - -#if ORTHANC_ENABLE_SSL == 1 -#include -#endif - -#define ORTHANC_REALM "Orthanc Secure Area" - - -namespace Orthanc -{ - static const char multipart[] = "multipart/form-data; boundary="; - static unsigned int multipartLength = sizeof(multipart) / sizeof(char) - 1; - - - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class MongooseOutputStream : public IHttpOutputStream - { - private: - struct mg_connection* connection_; - - public: - MongooseOutputStream(struct mg_connection* connection) : connection_(connection) - { - } - - virtual void Send(bool isHeader, const void* buffer, size_t length) - { - if (length > 0) - { - int status = mg_write(connection_, buffer, length); - if (status != static_cast(length)) - { - // status == 0 when the connection has been closed, -1 on error - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - } - - virtual void OnHttpStatusReceived(HttpStatus status) - { - // Ignore this - } - }; - - - enum PostDataStatus - { - PostDataStatus_Success, - PostDataStatus_NoLength, - PostDataStatus_Pending, - PostDataStatus_Failure - }; - } - - -// TODO Move this to external file - - - class ChunkedFile : public ChunkedBuffer - { - private: - std::string filename_; - - public: - ChunkedFile(const std::string& filename) : - filename_(filename) - { - } - - const std::string& GetFilename() const - { - return filename_; - } - }; - - - - class ChunkStore - { - private: - typedef std::list Content; - Content content_; - unsigned int numPlaces_; - - boost::mutex mutex_; - std::set discardedFiles_; - - void Clear() - { - for (Content::iterator it = content_.begin(); - it != content_.end(); ++it) - { - delete *it; - } - } - - Content::iterator Find(const std::string& filename) - { - for (Content::iterator it = content_.begin(); - it != content_.end(); ++it) - { - if ((*it)->GetFilename() == filename) - { - return it; - } - } - - return content_.end(); - } - - void Remove(const std::string& filename) - { - Content::iterator it = Find(filename); - if (it != content_.end()) - { - delete *it; - content_.erase(it); - } - } - - public: - ChunkStore() - { - numPlaces_ = 10; - } - - ~ChunkStore() - { - Clear(); - } - - PostDataStatus Store(std::string& completed, - const char* chunkData, - size_t chunkSize, - const std::string& filename, - size_t filesize) - { - boost::mutex::scoped_lock lock(mutex_); - - std::set::iterator wasDiscarded = discardedFiles_.find(filename); - if (wasDiscarded != discardedFiles_.end()) - { - discardedFiles_.erase(wasDiscarded); - return PostDataStatus_Failure; - } - - ChunkedFile* f; - Content::iterator it = Find(filename); - if (it == content_.end()) - { - f = new ChunkedFile(filename); - - // Make some room - if (content_.size() >= numPlaces_) - { - discardedFiles_.insert(content_.front()->GetFilename()); - delete content_.front(); - content_.pop_front(); - } - - content_.push_back(f); - } - else - { - f = *it; - } - - f->AddChunk(chunkData, chunkSize); - - if (f->GetNumBytes() > filesize) - { - Remove(filename); - } - else if (f->GetNumBytes() == filesize) - { - f->Flatten(completed); - Remove(filename); - return PostDataStatus_Success; - } - - return PostDataStatus_Pending; - } - - /*void Print() - { - boost::mutex::scoped_lock lock(mutex_); - - printf("ChunkStore status:\n"); - for (Content::const_iterator i = content_.begin(); - i != content_.end(); i++) - { - printf(" [%s]: %d\n", (*i)->GetFilename().c_str(), (*i)->GetNumBytes()); - } - printf("-----\n"); - }*/ - }; - - - struct MongooseServer::PImpl - { - struct mg_context *context_; - ChunkStore chunkStore_; - }; - - - ChunkStore& MongooseServer::GetChunkStore() - { - return pimpl_->chunkStore_; - } - - - - static PostDataStatus ReadBody(std::string& postData, - struct mg_connection *connection, - const IHttpHandler::Arguments& headers) - { - IHttpHandler::Arguments::const_iterator cs = headers.find("content-length"); - if (cs == headers.end()) - { - return PostDataStatus_NoLength; - } - - int length; - try - { - length = boost::lexical_cast(cs->second); - } - catch (boost::bad_lexical_cast&) - { - 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) - { - return PostDataStatus_Failure; - } - - assert(r <= length); - length -= r; - pos += r; - } - - return PostDataStatus_Success; - } - - - - static PostDataStatus ParseMultipartPost(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 postData; - PostDataStatus status = ReadBody(postData, connection, headers); - - if (status != PostDataStatus_Success) - { - return status; - } - - /*for (IHttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++) - { - std::cout << "Header [" << i->first << "] = " << i->second << "\n"; - } - printf("CHUNK\n");*/ - - typedef IHttpHandler::Arguments::const_iterator ArgumentIterator; - - ArgumentIterator requestedWith = headers.find("x-requested-with"); - ArgumentIterator fileName = headers.find("x-file-name"); - ArgumentIterator fileSizeStr = headers.find("x-file-size"); - - if (requestedWith != headers.end() && - requestedWith->second != "XMLHttpRequest") - { - return PostDataStatus_Failure; - } - - size_t fileSize = 0; - if (fileSizeStr != headers.end()) - { - try - { - fileSize = boost::lexical_cast(fileSizeStr->second); - } - catch (boost::bad_lexical_cast&) - { - return PostDataStatus_Failure; - } - } - - typedef boost::find_iterator FindIterator; - typedef boost::iterator_range Range; - - //chunkStore.Print(); - - try - { - FindIterator last; - for (FindIterator it = - make_find_iterator(postData, boost::first_finder(boundary)); - it!=FindIterator(); - ++it) - { - if (last != FindIterator()) - { - Range part(&last->back(), &it->front()); - Range content = boost::find_first(part, "\r\n\r\n"); - if (/*content != Range()*/!content.empty()) - { - Range c(&content.back() + 1, &it->front() - 2); - size_t chunkSize = c.size(); - - if (chunkSize > 0) - { - const char* chunkData = &c.front(); - - if (fileName == headers.end()) - { - // This file is stored in a single chunk - completedFile.resize(chunkSize); - if (chunkSize > 0) - { - memcpy(&completedFile[0], chunkData, chunkSize); - } - return PostDataStatus_Success; - } - else - { - return chunkStore.Store(completedFile, chunkData, chunkSize, fileName->second, fileSize); - } - } - } - } - - last = it; - } - } - catch (std::length_error&) - { - return PostDataStatus_Failure; - } - - return PostDataStatus_Pending; - } - - - static bool IsAccessGranted(const MongooseServer& that, - const IHttpHandler::Arguments& headers) - { - bool granted = false; - - IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); - if (auth != headers.end()) - { - std::string s = auth->second; - if (s.size() > 6 && - s.substr(0, 6) == "Basic ") - { - std::string b64 = s.substr(6); - granted = that.IsValidBasicHttpAuthentication(b64); - } - } - - return granted; - } - - - static std::string GetAuthenticatedUsername(const IHttpHandler::Arguments& headers) - { - IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); - - if (auth == headers.end()) - { - return ""; - } - - std::string s = auth->second; - if (s.size() <= 6 || - s.substr(0, 6) != "Basic ") - { - return ""; - } - - std::string b64 = s.substr(6); - std::string decoded; - Toolbox::DecodeBase64(decoded, b64); - size_t semicolons = decoded.find(':'); - - if (semicolons == std::string::npos) - { - // Bad-formatted request - return ""; - } - else - { - return decoded.substr(0, semicolons); - } - } - - - static bool ExtractMethod(HttpMethod& method, - const struct mg_request_info *request, - const IHttpHandler::Arguments& headers, - const IHttpHandler::GetArguments& argumentsGET) - { - std::string overriden; - - // Check whether some PUT/DELETE faking is done - - // 1. Faking with Google's approach - IHttpHandler::Arguments::const_iterator methodOverride = - headers.find("x-http-method-override"); - - if (methodOverride != headers.end()) - { - overriden = methodOverride->second; - } - else if (!strcmp(request->request_method, "GET")) - { - // 2. Faking with Ruby on Rail's approach - // GET /my/resource?_method=delete <=> DELETE /my/resource - for (size_t i = 0; i < argumentsGET.size(); i++) - { - if (argumentsGET[i].first == "_method") - { - overriden = argumentsGET[i].second; - break; - } - } - } - - if (overriden.size() > 0) - { - // A faking has been done within this request - Toolbox::ToUpperCase(overriden); - - LOG(INFO) << "HTTP method faking has been detected for " << overriden; - - if (overriden == "PUT") - { - method = HttpMethod_Put; - return true; - } - else if (overriden == "DELETE") - { - method = HttpMethod_Delete; - return true; - } - else - { - return false; - } - } - - // No PUT/DELETE faking was present - if (!strcmp(request->request_method, "GET")) - { - method = HttpMethod_Get; - } - else if (!strcmp(request->request_method, "POST")) - { - method = HttpMethod_Post; - } - else if (!strcmp(request->request_method, "DELETE")) - { - method = HttpMethod_Delete; - } - else if (!strcmp(request->request_method, "PUT")) - { - method = HttpMethod_Put; - } - else - { - return false; - } - - return true; - } - - - static void ConfigureHttpCompression(HttpOutput& output, - const IHttpHandler::Arguments& headers) - { - // Look if the client wishes HTTP compression - // https://en.wikipedia.org/wiki/HTTP_compression - IHttpHandler::Arguments::const_iterator it = headers.find("accept-encoding"); - if (it != headers.end()) - { - std::vector encodings; - Toolbox::TokenizeString(encodings, it->second, ','); - - for (size_t i = 0; i < encodings.size(); i++) - { - std::string s = Toolbox::StripSpaces(encodings[i]); - - if (s == "deflate") - { - output.SetDeflateAllowed(true); - } - else if (s == "gzip") - { - output.SetGzipAllowed(true); - } - } - } - } - - - static void InternalCallback(HttpOutput& output /* out */, - HttpMethod& method /* out */, - MongooseServer& server, - struct mg_connection *connection, - const struct mg_request_info *request) - { - bool localhost; - -#if ORTHANC_ENABLE_MONGOOSE == 1 - static const long LOCALHOST = (127ll << 24) + 1ll; - localhost = (request->remote_ip == LOCALHOST); -#elif ORTHANC_ENABLE_CIVETWEB == 1 - // The "remote_ip" field of "struct mg_request_info" is tagged as - // deprecated in Civetweb, using "remote_addr" instead. - localhost = (std::string(request->remote_addr) == "127.0.0.1"); -#else -# error -#endif - - // Check remote calls - if (!server.IsRemoteAccessAllowed() && - !localhost) - { - output.SendUnauthorized(server.GetRealm()); - return; - } - - - // Extract the HTTP headers - IHttpHandler::Arguments headers; - for (int i = 0; i < request->num_headers; i++) - { - std::string name = request->http_headers[i].name; - std::string value = request->http_headers[i].value; - - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - headers.insert(std::make_pair(name, value)); - VLOG(1) << "HTTP header: [" << name << "]: [" << value << "]"; - } - - if (server.IsHttpCompressionEnabled()) - { - ConfigureHttpCompression(output, headers); - } - - - // Extract the GET arguments - IHttpHandler::GetArguments argumentsGET; - if (!strcmp(request->request_method, "GET")) - { - HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); - } - - - // Compute the HTTP method, taking method faking into consideration - method = HttpMethod_Get; - if (!ExtractMethod(method, request, headers, argumentsGET)) - { - output.SendStatus(HttpStatus_400_BadRequest); - return; - } - - - // Authenticate this connection - if (server.IsAuthenticationEnabled() && - !IsAccessGranted(server, headers)) - { - output.SendUnauthorized(server.GetRealm()); - return; - } - - -#if ORTHANC_ENABLE_MONGOOSE == 1 - // Apply the filter, if it is installed - char remoteIp[24]; - sprintf(remoteIp, "%d.%d.%d.%d", - reinterpret_cast(&request->remote_ip) [3], - reinterpret_cast(&request->remote_ip) [2], - reinterpret_cast(&request->remote_ip) [1], - reinterpret_cast(&request->remote_ip) [0]); - - const char* requestUri = request->uri; - -#elif ORTHANC_ENABLE_CIVETWEB == 1 - const char* remoteIp = request->remote_addr; - const char* requestUri = request->local_uri; -#else -# error -#endif - - if (requestUri == NULL) - { - requestUri = ""; - } - - std::string username = GetAuthenticatedUsername(headers); - - IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); - if (filter != NULL) - { - if (!filter->IsAllowed(method, requestUri, remoteIp, - username.c_str(), headers, argumentsGET)) - { - //output.SendUnauthorized(server.GetRealm()); - output.SendStatus(HttpStatus_403_Forbidden); - return; - } - } - - - // Extract the body of the request for PUT and POST - - // TODO Avoid unneccessary memcopy of the body - - std::string body; - if (method == HttpMethod_Post || - method == HttpMethod_Put) - { - PostDataStatus status; - - IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); - if (ct == headers.end()) - { - // No content-type specified. Assume no multi-part content occurs at this point. - status = ReadBody(body, connection, headers); - } - else - { - std::string contentType = ct->second; - if (contentType.size() >= multipartLength && - !memcmp(contentType.c_str(), multipart, multipartLength)) - { - status = ParseMultipartPost(body, connection, headers, contentType, server.GetChunkStore()); - } - else - { - status = ReadBody(body, connection, headers); - } - } - - switch (status) - { - case PostDataStatus_NoLength: - output.SendStatus(HttpStatus_411_LengthRequired); - return; - - case PostDataStatus_Failure: - output.SendStatus(HttpStatus_400_BadRequest); - return; - - case PostDataStatus_Pending: - output.AnswerEmpty(); - return; - - default: - break; - } - } - - - // 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()) - { - found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), - method, uri, headers, argumentsGET, body.c_str(), body.size()); - } - - if (!found) - { - throw OrthancException(ErrorCode_UnknownResource); - } - } - - - static void ProtectedCallback(struct mg_connection *connection, - const struct mg_request_info *request) - { - try - { -#if ORTHANC_ENABLE_MONGOOSE == 1 - void *that = request->user_data; - const char* requestUri = request->uri; -#elif ORTHANC_ENABLE_CIVETWEB == 1 - // https://github.com/civetweb/civetweb/issues/409 - void *that = mg_get_user_data(mg_get_context(connection)); - const char* requestUri = request->local_uri; -#else -# error -#endif - - if (requestUri == NULL) - { - requestUri = ""; - } - - MongooseServer* server = reinterpret_cast(that); - - if (server == NULL) - { - MongooseOutputStream stream(connection); - HttpOutput output(stream, false /* assume no keep-alive */); - output.SendStatus(HttpStatus_500_InternalServerError); - return; - } - - MongooseOutputStream stream(connection); - HttpOutput output(stream, server->IsKeepAliveEnabled()); - HttpMethod method = HttpMethod_Get; - - try - { - try - { - InternalCallback(output, method, *server, connection, request); - } - catch (OrthancException&) - { - throw; // Pass the exception to the main handler below - } - // Now convert native exceptions as OrthancException - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_BadParameterType, - "Syntax error in some user-supplied data"); - } - catch (std::runtime_error&) - { - // Presumably an error while parsing the JSON body - throw OrthancException(ErrorCode_BadRequest); - } - catch (std::bad_alloc&) - { - throw OrthancException(ErrorCode_NotEnoughMemory, - "The server hosting Orthanc is running out of memory"); - } - catch (...) - { - throw OrthancException(ErrorCode_InternalError, - "An unhandled exception was generated inside the HTTP server"); - } - } - catch (OrthancException& e) - { - assert(server != NULL); - - // Using this candidate handler results in an exception - try - { - if (server->GetExceptionFormatter() == NULL) - { - LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); - output.SendStatus(e.GetHttpStatus()); - } - else - { - server->GetExceptionFormatter()->Format(output, e, method, requestUri); - } - } - catch (OrthancException&) - { - // An exception here reflects the fact that the status code - // was already set by the HTTP handler. - } - } - } - catch (...) - { - // We should never arrive at this point, where it is even impossible to send an answer - LOG(ERROR) << "Catastrophic error inside the HTTP server, giving up"; - } - } - - -#if MONGOOSE_USE_CALLBACKS == 0 - static void* Callback(enum mg_event event, - struct mg_connection *connection, - const struct mg_request_info *request) - { - if (event == MG_NEW_REQUEST) - { - ProtectedCallback(connection, request); - - // Mark as processed - return (void*) ""; - } - else - { - return NULL; - } - } - -#elif MONGOOSE_USE_CALLBACKS == 1 - static int Callback(struct mg_connection *connection) - { - const struct mg_request_info *request = mg_get_request_info(connection); - - ProtectedCallback(connection, request); - - return 1; // Do not let Mongoose handle the request by itself - } - -#else -# error Please set MONGOOSE_USE_CALLBACKS -#endif - - - - - - bool MongooseServer::IsRunning() const - { - return (pimpl_->context_ != NULL); - } - - - MongooseServer::MongooseServer() : pimpl_(new PImpl) - { - pimpl_->context_ = NULL; - handler_ = NULL; - remoteAllowed_ = false; - authentication_ = false; - ssl_ = false; - port_ = 8000; - filter_ = NULL; - keepAlive_ = false; - httpCompression_ = true; - exceptionFormatter_ = NULL; - realm_ = ORTHANC_REALM; - threadsCount_ = 50; // Default value in mongoose - -#if ORTHANC_ENABLE_SSL == 1 - // Check for the Heartbleed exploit - // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug - if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ && - OPENSSL_VERSION_NUMBER >= 0x1000100fL /* openssl-1.0.1 */) - { - LOG(WARNING) << "This version of OpenSSL is vulnerable to the Heartbleed exploit"; - } -#endif - } - - - MongooseServer::~MongooseServer() - { - Stop(); - } - - - void MongooseServer::SetPortNumber(uint16_t port) - { - Stop(); - port_ = port; - } - - void MongooseServer::Start() - { -#if ORTHANC_ENABLE_MONGOOSE == 1 - LOG(INFO) << "Starting embedded Web server using Mongoose"; -#elif ORTHANC_ENABLE_CIVETWEB == 1 - LOG(INFO) << "Starting embedded Web server using Civetweb"; -#else -# error -#endif - - if (!IsRunning()) - { - std::string port = boost::lexical_cast(port_); - std::string numThreads = boost::lexical_cast(threadsCount_); - - if (ssl_) - { - port += "s"; - } - - const char *options[] = { - // Set the TCP port for the HTTP server - "listening_ports", port.c_str(), - - // Optimization reported by Chris Hafey - // https://groups.google.com/d/msg/orthanc-users/CKueKX0pJ9E/_UCbl8T-VjIJ - "enable_keep_alive", (keepAlive_ ? "yes" : "no"), - -#if ORTHANC_ENABLE_CIVETWEB == 1 - // https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md#enable_keep_alive-no - "keep_alive_timeout_ms", (keepAlive_ ? "500" : "0"), -#endif - - // Set the number of threads - "num_threads", numThreads.c_str(), - - // Set the SSL certificate, if any. This must be the last option. - ssl_ ? "ssl_certificate" : NULL, - certificate_.c_str(), - NULL - }; - -#if MONGOOSE_USE_CALLBACKS == 0 - pimpl_->context_ = mg_start(&Callback, this, options); - -#elif MONGOOSE_USE_CALLBACKS == 1 - struct mg_callbacks callbacks; - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = Callback; - pimpl_->context_ = mg_start(&callbacks, this, options); - -#else -# error Please set MONGOOSE_USE_CALLBACKS -#endif - - if (!pimpl_->context_) - { - throw OrthancException(ErrorCode_HttpPortInUse); - } - - LOG(WARNING) << "HTTP server listening on port: " << GetPortNumber() - << " (HTTPS encryption is " - << (IsSslEnabled() ? "enabled" : "disabled") - << ", remote access is " - << (IsRemoteAccessAllowed() ? "" : "not ") - << "allowed)"; - } - } - - void MongooseServer::Stop() - { - if (IsRunning()) - { - mg_stop(pimpl_->context_); - pimpl_->context_ = NULL; - } - } - - - void MongooseServer::ClearUsers() - { - Stop(); - registeredUsers_.clear(); - } - - - void MongooseServer::RegisterUser(const char* username, - const char* password) - { - Stop(); - - std::string tag = std::string(username) + ":" + std::string(password); - std::string encoded; - Toolbox::EncodeBase64(encoded, tag); - registeredUsers_.insert(encoded); - } - - void MongooseServer::SetSslEnabled(bool enabled) - { - Stop(); - -#if ORTHANC_ENABLE_SSL == 0 - if (enabled) - { - throw OrthancException(ErrorCode_SslDisabled); - } - else - { - ssl_ = false; - } -#else - ssl_ = enabled; -#endif - } - - - void MongooseServer::SetKeepAliveEnabled(bool enabled) - { - Stop(); - keepAlive_ = enabled; - LOG(INFO) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled"); - } - - - void MongooseServer::SetAuthenticationEnabled(bool enabled) - { - Stop(); - authentication_ = enabled; - } - - void MongooseServer::SetSslCertificate(const char* path) - { - Stop(); - certificate_ = path; - } - - void MongooseServer::SetRemoteAccessAllowed(bool allowed) - { - Stop(); - remoteAllowed_ = allowed; - } - - void MongooseServer::SetHttpCompressionEnabled(bool enabled) - { - Stop(); - httpCompression_ = enabled; - LOG(WARNING) << "HTTP compression is " << (enabled ? "enabled" : "disabled"); - } - - void MongooseServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter) - { - Stop(); - filter_ = &filter; - } - - - void MongooseServer::SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter) - { - Stop(); - exceptionFormatter_ = &formatter; - } - - - bool MongooseServer::IsValidBasicHttpAuthentication(const std::string& basic) const - { - return registeredUsers_.find(basic) != registeredUsers_.end(); - } - - - void MongooseServer::Register(IHttpHandler& handler) - { - Stop(); - handler_ = &handler; - } - - - IHttpHandler& MongooseServer::GetHandler() const - { - if (handler_ == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *handler_; - } - - - void MongooseServer::SetThreadsCount(unsigned int threads) - { - if (threads <= 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - Stop(); - threadsCount_ = threads; - } -} diff -r 57478347f846 -r af4fab776ff2 Core/HttpServer/MongooseServer.h --- a/Core/HttpServer/MongooseServer.h Wed Jan 16 15:54:16 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,210 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_MONGOOSE) -# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to include this file -#endif - -#if !defined(ORTHANC_ENABLE_CIVETWEB) -# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to include this file -#endif - -#if (ORTHANC_ENABLE_MONGOOSE == 0 && \ - ORTHANC_ENABLE_CIVETWEB == 0) -# error Either ORTHANC_ENABLE_MONGOOSE or ORTHANC_ENABLE_CIVETWEB must be set to 1 -#endif - - -#include "IIncomingHttpRequestFilter.h" - -#include -#include -#include -#include -#include - -namespace Orthanc -{ - class ChunkStore; - class OrthancException; - - class IHttpExceptionFormatter : public boost::noncopyable - { - public: - virtual ~IHttpExceptionFormatter() - { - } - - virtual void Format(HttpOutput& output, - const OrthancException& exception, - HttpMethod method, - const char* uri) = 0; - }; - - - class MongooseServer - { - private: - // http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom - struct PImpl; - boost::shared_ptr pimpl_; - - IHttpHandler *handler_; - - typedef std::set RegisteredUsers; - RegisteredUsers registeredUsers_; - - bool remoteAllowed_; - bool authentication_; - bool ssl_; - std::string certificate_; - uint16_t port_; - IIncomingHttpRequestFilter* filter_; - bool keepAlive_; - bool httpCompression_; - IHttpExceptionFormatter* exceptionFormatter_; - std::string realm_; - unsigned int threadsCount_; - - bool IsRunning() const; - - public: - MongooseServer(); - - ~MongooseServer(); - - void SetPortNumber(uint16_t port); - - uint16_t GetPortNumber() const - { - return port_; - } - - void Start(); - - void Stop(); - - void ClearUsers(); - - void RegisterUser(const char* username, - const char* password); - - bool IsAuthenticationEnabled() const - { - return authentication_; - } - - void SetAuthenticationEnabled(bool enabled); - - bool IsSslEnabled() const - { - return ssl_; - } - - void SetSslEnabled(bool enabled); - - bool IsKeepAliveEnabled() const - { - return keepAlive_; - } - - void SetKeepAliveEnabled(bool enabled); - - const std::string& GetSslCertificate() const - { - return certificate_; - } - - void SetSslCertificate(const char* path); - - bool IsRemoteAccessAllowed() const - { - return remoteAllowed_; - } - - void SetRemoteAccessAllowed(bool allowed); - - bool IsHttpCompressionEnabled() const - { - return httpCompression_;; - } - - void SetHttpCompressionEnabled(bool enabled); - - IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const - { - return filter_; - } - - void SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter); - - ChunkStore& GetChunkStore(); - - bool IsValidBasicHttpAuthentication(const std::string& basic) const; - - void Register(IHttpHandler& handler); - - bool HasHandler() const - { - return handler_ != NULL; - } - - IHttpHandler& GetHandler() const; - - void SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter); - - IHttpExceptionFormatter* GetExceptionFormatter() - { - return exceptionFormatter_; - } - - const std::string& GetRealm() const - { - return realm_; - } - - void SetRealm(const std::string& realm) - { - realm_ = realm; - } - - void SetThreadsCount(unsigned int threads); - - unsigned int GetThreadsCount() const - { - return threadsCount_; - } - }; -} diff -r 57478347f846 -r af4fab776ff2 LinuxCompilation.txt --- a/LinuxCompilation.txt Wed Jan 16 15:54:16 2019 +0100 +++ b/LinuxCompilation.txt Thu Jan 17 18:30:52 2019 +0100 @@ -85,7 +85,7 @@ libboost-all-dev libwrap0-dev libjsoncpp-dev libpugixml-dev # cmake -DALLOW_DOWNLOADS=ON \ - -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_CIVETWEB=OFF \ -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ -DDCMTK_LIBRARIES=dcmjpls \ -DCMAKE_BUILD_TYPE=Release \ @@ -106,7 +106,7 @@ # cmake "-DDCMTK_LIBRARIES=boost_locale;CharLS;dcmjpls;wrap;oflog" \ -DALLOW_DOWNLOADS=ON \ - -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_CIVETWEB=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_SYSTEM_PUGIXML=OFF \ -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ @@ -126,7 +126,7 @@ # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ - -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_CIVETWEB=OFF \ -DDCMTK_LIBRARIES=dcmjpls \ -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc @@ -146,6 +146,7 @@ # sudo yum install gflags-devel # cmake "-DDCMTK_LIBRARIES=CharLS" \ + -DENABLE_CIVETWEB=OFF \ -DSYSTEM_MONGOOSE_USE_CALLBACKS=OFF \ -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc @@ -162,7 +163,7 @@ e2fsprogs-libuuid boost-libs sqlite3 python libiconv # cmake -DALLOW_DOWNLOADS=ON \ - -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_CIVETWEB=OFF \ -DDCMTK_LIBRARIES="dcmdsig;charls;dcmjpls" \ -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc @@ -178,7 +179,7 @@ # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_JSONCPP=OFF \ - -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_CIVETWEB=OFF \ -DUSE_SYSTEM_PUGIXML=OFF \ -DUSE_SYSTEM_SQLITE=OFF \ -DUSE_SYSTEM_BOOST=OFF \ diff -r 57478347f846 -r af4fab776ff2 NEWS --- a/NEWS Wed Jan 16 15:54:16 2019 +0100 +++ b/NEWS Thu Jan 17 18:30:52 2019 +0100 @@ -1,6 +1,12 @@ Pending changes in the mainline =============================== +General +------- + +* CivetWeb is now the default embedded HTTP server (instead of Mongoose) +* New configuration option: "TcpNoDelay" to disable Nagle's algorithm in HTTP server + REST API -------- diff -r 57478347f846 -r af4fab776ff2 OrthancServer/OrthancConfiguration.cpp --- a/OrthancServer/OrthancConfiguration.cpp Wed Jan 16 15:54:16 2019 +0100 +++ b/OrthancServer/OrthancConfiguration.cpp Thu Jan 17 18:30:52 2019 +0100 @@ -34,7 +34,7 @@ #include "PrecompiledHeadersServer.h" #include "OrthancConfiguration.h" -#include "../Core/HttpServer/MongooseServer.h" +#include "../Core/HttpServer/HttpServer.h" #include "../Core/Logging.h" #include "../Core/OrthancException.h" #include "../Core/SystemToolbox.h" @@ -609,7 +609,7 @@ } - void OrthancConfiguration::SetupRegisteredUsers(MongooseServer& httpServer) const + void OrthancConfiguration::SetupRegisteredUsers(HttpServer& httpServer) const { httpServer.ClearUsers(); diff -r 57478347f846 -r af4fab776ff2 OrthancServer/OrthancConfiguration.h --- a/OrthancServer/OrthancConfiguration.h Wed Jan 16 15:54:16 2019 +0100 +++ b/OrthancServer/OrthancConfiguration.h Thu Jan 17 18:30:52 2019 +0100 @@ -45,7 +45,7 @@ namespace Orthanc { - class MongooseServer; + class HttpServer; class ServerIndex; class OrthancConfiguration : public boost::noncopyable @@ -184,7 +184,7 @@ void GetListOfOrthancPeers(std::set& target) const; - void SetupRegisteredUsers(MongooseServer& httpServer) const; + void SetupRegisteredUsers(HttpServer& httpServer) const; std::string InterpretStringParameterAsPath(const std::string& parameter) const; diff -r 57478347f846 -r af4fab776ff2 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Wed Jan 16 15:54:16 2019 +0100 +++ b/OrthancServer/main.cpp Thu Jan 17 18:30:52 2019 +0100 @@ -41,7 +41,7 @@ #include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/HttpServer/EmbeddedResourceHttpHandler.h" #include "../Core/HttpServer/FilesystemHttpHandler.h" -#include "../Core/HttpServer/MongooseServer.h" +#include "../Core/HttpServer/HttpServer.h" #include "../Core/Logging.h" #include "../Core/Lua/LuaFunctionCall.h" #include "../Plugins/Engine/OrthancPlugins.h" @@ -797,9 +797,17 @@ else { MyIncomingHttpRequestFilter httpFilter(context, plugins); - MongooseServer httpServer; + HttpServer httpServer; bool httpDescribeErrors; +#if ORTHANC_ENABLE_MONGOOSE == 1 + const bool defaultKeepAlive = false; +#elif ORTHANC_ENABLE_CIVETWEB == 1 + const bool defaultKeepAlive = true; +#else +# error "Either Mongoose or Civetweb must be enabled to compile this file" +#endif + { OrthancConfiguration::ReaderLock lock; @@ -809,9 +817,10 @@ //httpServer.SetThreadsCount(50); httpServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042)); httpServer.SetRemoteAccessAllowed(lock.GetConfiguration().GetBooleanParameter("RemoteAccessAllowed", false)); - httpServer.SetKeepAliveEnabled(lock.GetConfiguration().GetBooleanParameter("KeepAlive", true)); + httpServer.SetKeepAliveEnabled(lock.GetConfiguration().GetBooleanParameter("KeepAlive", defaultKeepAlive)); httpServer.SetHttpCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("HttpCompressionEnabled", true)); httpServer.SetAuthenticationEnabled(lock.GetConfiguration().GetBooleanParameter("AuthenticationEnabled", false)); + httpServer.SetTcpNoDelay(lock.GetConfiguration().GetBooleanParameter("TcpNoDelay", true)); lock.GetConfiguration().SetupRegisteredUsers(httpServer); @@ -1021,7 +1030,7 @@ catch (OrthancException&) { LOG(ERROR) << "Unable to run the automated upgrade, please use the replication instructions: " - << "http://book.orthanc-server.com//users/replication.html"; + << "http://book.orthanc-server.com/users/replication.html"; throw; } diff -r 57478347f846 -r af4fab776ff2 Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed Jan 16 15:54:16 2019 +0100 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Thu Jan 17 18:30:52 2019 +0100 @@ -271,9 +271,9 @@ ${ORTHANC_ROOT}/Core/HttpServer/HttpContentNegociation.cpp ${ORTHANC_ROOT}/Core/HttpServer/HttpFileSender.cpp ${ORTHANC_ROOT}/Core/HttpServer/HttpOutput.cpp + ${ORTHANC_ROOT}/Core/HttpServer/HttpServer.cpp ${ORTHANC_ROOT}/Core/HttpServer/HttpStreamTranscoder.cpp ${ORTHANC_ROOT}/Core/HttpServer/HttpToolbox.cpp - ${ORTHANC_ROOT}/Core/HttpServer/MongooseServer.cpp ${ORTHANC_ROOT}/Core/HttpServer/StringHttpOutput.cpp ${ORTHANC_ROOT}/Core/RestApi/RestApi.cpp ${ORTHANC_ROOT}/Core/RestApi/RestApiCall.cpp diff -r 57478347f846 -r af4fab776ff2 Resources/CMake/OrthancFrameworkParameters.cmake --- a/Resources/CMake/OrthancFrameworkParameters.cmake Wed Jan 16 15:54:16 2019 +0100 +++ b/Resources/CMake/OrthancFrameworkParameters.cmake Thu Jan 17 18:30:52 2019 +0100 @@ -30,7 +30,7 @@ set(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") # Generic parameters of the build -set(ENABLE_CIVETWEB OFF CACHE BOOL "Use Civetweb instead of Mongoose") +set(ENABLE_CIVETWEB ON CACHE BOOL "Use Civetweb instead of Mongoose (Mongoose was the default embedded HTTP server in Orthanc <= 1.5.1)") set(ENABLE_PKCS11 OFF CACHE BOOL "Enable PKCS#11 for HTTPS client authentication using hardware security modules and smart cards") set(ENABLE_PROFILING OFF CACHE BOOL "Whether to enable the generation of profiling information with gprof") set(ENABLE_SSL ON CACHE BOOL "Include support for SSL") diff -r 57478347f846 -r af4fab776ff2 Resources/Configuration.json --- a/Resources/Configuration.json Wed Jan 16 15:54:16 2019 +0100 +++ b/Resources/Configuration.json Thu Jan 17 18:30:52 2019 +0100 @@ -349,11 +349,20 @@ // Enable or disable HTTP Keep-Alive (persistent HTTP // connections). Setting this option to "true" prevents Orthanc // issue #32 ("HttpServer does not support multiple HTTP requests in - // the same TCP stream"), but can slow down HTTP clients that do not - // support persistent connections. The default behavior used to be - // "false" in Orthanc <= 1.5.1. + // the same TCP stream"), but can possibly slow down HTTP clients + // that do not support persistent connections. The default behavior + // used to be "false" in Orthanc <= 1.5.1. Setting this option to + // "false" is also recommended if Orthanc is compiled against + // Mongoose. "KeepAlive" : true, + // Enable or disable Nagle's algorithm. Only taken into + // consideration if Orthanc is compiled to use CivetWeb. Experiments + // show that best performance can be obtained by setting both + // "KeepAlive" and "TcpNoDelay" to "true". Beware however of + // caveats: https://eklitzke.org/the-caveats-of-tcp-nodelay + "TcpNoDelay" : true, + // If this option is set to "false", Orthanc will run in index-only // mode. The DICOM files will not be stored on the drive. Note that // this option might prevent the upgrade to newer versions of Orthanc. diff -r 57478347f846 -r af4fab776ff2 Resources/DownloadOrthancFramework.cmake --- a/Resources/DownloadOrthancFramework.cmake Wed Jan 16 15:54:16 2019 +0100 +++ b/Resources/DownloadOrthancFramework.cmake Thu Jan 17 18:30:52 2019 +0100 @@ -229,8 +229,7 @@ else() # Default case: Download from the official Web site set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz) - #set(ORTHANC_FRAMEWORK_URL "http://www.orthanc-server.com/downloads/get.php?path=/orthanc/${ORTHANC_FRAMEMORK_FILENAME}") - set(ORTHANC_FRAMEWORK_URL "http://www.orthanc-server.com/downloads/third-party/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}") + set(ORTHANC_FRAMEWORK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}") endif() set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}") diff -r 57478347f846 -r af4fab776ff2 Resources/WebAssembly/ArithmeticTests/CMakeLists.txt --- a/Resources/WebAssembly/ArithmeticTests/CMakeLists.txt Wed Jan 16 15:54:16 2019 +0100 +++ b/Resources/WebAssembly/ArithmeticTests/CMakeLists.txt Thu Jan 17 18:30:52 2019 +0100 @@ -31,7 +31,7 @@ include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) set(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.2) -set(DCMTK_URL "http://www.orthanc-server.com/downloads/third-party/dcmtk-3.6.2.tar.gz") +set(DCMTK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dcmtk-3.6.2.tar.gz") set(DCMTK_MD5 "d219a4152772985191c9b89d75302d12") if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}")