//////////////////////////////////////////////////////////////////////////////// /// @brief http server /// /// @file /// /// DISCLAIMER /// /// Copyright 2014-2015 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Dr. Frank Celler /// @author Achim Brandt /// @author Copyright 2014-2015, ArangoDB GmbH, Cologne, Germany /// @author Copyright 2009-2014, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "HttpServer.h" #include "Basics/Mutex.h" #include "Basics/MutexLocker.h" #include "Basics/logging.h" #include "Dispatcher/Dispatcher.h" #include "HttpServer/AsyncJobManager.h" #include "HttpServer/HttpCommTask.h" #include "HttpServer/HttpHandler.h" #include "HttpServer/HttpListenTask.h" #include "HttpServer/HttpServerJob.h" #include "Rest/EndpointList.h" #include "Scheduler/ListenTask.h" #include "Scheduler/Scheduler.h" using namespace triagens::basics; using namespace triagens::rest; using namespace std; #ifdef TRI_USE_SPIN_LOCK_GENERAL_SERVER #define GENERAL_SERVER_LOCKER(a) SPIN_LOCKER(a) #else #define GENERAL_SERVER_LOCKER(a) MUTEX_LOCKER(a) #endif // ----------------------------------------------------------------------------- // --SECTION-- private static variables // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief map of ChunkedTask //////////////////////////////////////////////////////////////////////////////// static std::unordered_map HttpCommTaskMap; //////////////////////////////////////////////////////////////////////////////// /// @brief lock for the above map //////////////////////////////////////////////////////////////////////////////// static Mutex HttpCommTaskMapLock; // ----------------------------------------------------------------------------- // --SECTION-- class HttpServer // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- static public methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief destroys an endpoint server //////////////////////////////////////////////////////////////////////////////// int HttpServer::sendChunk (uint64_t taskId, const string& data) { MUTEX_LOCKER(HttpCommTaskMapLock); auto&& it = HttpCommTaskMap.find(taskId); if (it == HttpCommTaskMap.end()) { return TRI_ERROR_TASK_NOT_FOUND; } return it->second->signalChunk(data); } // ----------------------------------------------------------------------------- // --SECTION-- constructors and destructors // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief constructs a new general server with dispatcher and job manager //////////////////////////////////////////////////////////////////////////////// HttpServer::HttpServer (Scheduler* scheduler, Dispatcher* dispatcher, HttpHandlerFactory* handlerFactory, AsyncJobManager* jobManager, double keepAliveTimeout) : _scheduler(scheduler), _dispatcher(dispatcher), _handlerFactory(handlerFactory), _jobManager(jobManager), _listenTasks(), _endpointList(nullptr), _commTasks(), _keepAliveTimeout(keepAliveTimeout) { } //////////////////////////////////////////////////////////////////////////////// /// @brief destructs a general server //////////////////////////////////////////////////////////////////////////////// HttpServer::~HttpServer () { for (auto& task : _commTasks) { unregisterChunkedTask(task); _scheduler->destroyTask(task); } stopListening(); } // ----------------------------------------------------------------------------- // --SECTION-- virtual public methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief generates a suitable communication task //////////////////////////////////////////////////////////////////////////////// HttpCommTask* HttpServer::createCommTask (TRI_socket_t s, const ConnectionInfo& info) { return new HttpCommTask(this, s, info, _keepAliveTimeout); } // ----------------------------------------------------------------------------- // --SECTION-- public methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief add the endpoint list //////////////////////////////////////////////////////////////////////////////// void HttpServer::setEndpointList (const EndpointList* list) { _endpointList = list; } //////////////////////////////////////////////////////////////////////////////// /// @brief starts listening //////////////////////////////////////////////////////////////////////////////// void HttpServer::startListening () { auto endpoints = _endpointList->getByPrefix(encryptionType()); for (auto&& i : endpoints) { LOG_TRACE("trying to bind to endpoint '%s' for requests", i.first.c_str()); bool ok = openEndpoint(i.second); if (ok) { LOG_DEBUG("bound to endpoint '%s'", i.first.c_str()); } else { LOG_FATAL_AND_EXIT("failed to bind to endpoint '%s'. Please check whether another instance is already running or review your endpoints configuration.", i.first.c_str()); } } } //////////////////////////////////////////////////////////////////////////////// /// @brief stops listening //////////////////////////////////////////////////////////////////////////////// void HttpServer::stopListening () { for (auto& task : _listenTasks) { _scheduler->destroyTask(task); } _listenTasks.clear(); } //////////////////////////////////////////////////////////////////////////////// /// @brief registers a chunked task //////////////////////////////////////////////////////////////////////////////// void HttpServer::registerChunkedTask (HttpCommTask* task, ssize_t n) { auto id = task->taskId(); MUTEX_LOCKER(HttpCommTaskMapLock); HttpCommTaskMap[id] = task; } //////////////////////////////////////////////////////////////////////////////// /// @brief unregisters a chunked task //////////////////////////////////////////////////////////////////////////////// void HttpServer::unregisterChunkedTask (HttpCommTask* task) { auto id = task->taskId(); MUTEX_LOCKER(HttpCommTaskMapLock); HttpCommTaskMap.erase(id); } //////////////////////////////////////////////////////////////////////////////// /// @brief removes all listen and comm tasks //////////////////////////////////////////////////////////////////////////////// void HttpServer::stop () { while (true) { HttpCommTask* task = nullptr; { GENERAL_SERVER_LOCKER(_commTasksLock); if (_commTasks.empty()) { break; } task = *_commTasks.begin(); _commTasks.erase(task); } unregisterChunkedTask(task); _scheduler->destroyTask(task); } } //////////////////////////////////////////////////////////////////////////////// /// @brief handles connection request //////////////////////////////////////////////////////////////////////////////// void HttpServer::handleConnected (TRI_socket_t s, const ConnectionInfo& info) { HttpCommTask* task = createCommTask(s, info); try { GENERAL_SERVER_LOCKER(_commTasksLock); _commTasks.emplace(task); } catch (...) { // destroy the task to prevent a leak deleteTask(task); throw; } // registers the task and get the number of the scheduler thread ssize_t n; int res = _scheduler->registerTask(task, &n); // register the ChunkedTask in the same thread if (res == TRI_ERROR_NO_ERROR) { registerChunkedTask(task, n); } task->setupDone(); } //////////////////////////////////////////////////////////////////////////////// /// @brief handles a connection close //////////////////////////////////////////////////////////////////////////////// void HttpServer::handleCommunicationClosed (HttpCommTask* task) { shutdownHandlerByTask(task); { GENERAL_SERVER_LOCKER(_commTasksLock); _commTasks.erase(task); } unregisterChunkedTask(task); } //////////////////////////////////////////////////////////////////////////////// /// @brief handles a connection failure //////////////////////////////////////////////////////////////////////////////// void HttpServer::handleCommunicationFailure (HttpCommTask* task) { shutdownHandlerByTask(task); { GENERAL_SERVER_LOCKER(_commTasksLock); _commTasks.erase(task); } unregisterChunkedTask(task); } //////////////////////////////////////////////////////////////////////////////// /// @brief callback if the handler received a signal //////////////////////////////////////////////////////////////////////////////// void HttpServer::handleAsync (HttpCommTask* task) { task->processRead(); } //////////////////////////////////////////////////////////////////////////////// /// @brief create a job for asynchronous execution (using the dispatcher) //////////////////////////////////////////////////////////////////////////////// bool HttpServer::handleRequestAsync (std::unique_ptr& handler, uint64_t* jobId) { // execute the handler using the dispatcher std::unique_ptr job(new HttpServerJob(this, handler.get(), nullptr)); // handler now belongs to the job auto h = handler.release(); // now handler.get() is a nullptr and must not be accessed if (jobId != nullptr) { try { _jobManager->initAsyncJob(job.get(), jobId); } catch (...) { RequestStatisticsAgentSetExecuteError(h); LOG_WARNING("unable to initialize job"); return false; } } int error = _dispatcher->addJob(job.get()); if (error != TRI_ERROR_NO_ERROR) { // could not add job to job queue RequestStatisticsAgentSetExecuteError(h); LOG_WARNING("unable to add job to the job queue: %s", TRI_errno_string(error)); return false; } // job now belongs to the dispatcher job.release(); // job is in queue now return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief executes the handler directly or add it to the queue //////////////////////////////////////////////////////////////////////////////// bool HttpServer::handleRequest (HttpCommTask* task, std::unique_ptr& handler) { // execute handler and (possibly) requeue while (true) { // directly execute the handler within the scheduler thread if (handler->isDirect()) { HttpHandler::status_t status = handleRequestDirectly(task, handler.get()); if (status.status != HttpHandler::HANDLER_REQUEUE) { return true; } } // execute the handler using the dispatcher else { std::unique_ptr job(new HttpServerJob(this, handler.get(), task)); // handler now belongs to the job auto h = handler.release(); h->RequestStatisticsAgent::transfer(job.get()); task->setCurrentJob(job.get()); if (_dispatcher->addJob(job.get()) != TRI_ERROR_NO_ERROR) { task->clearCurrentJob(); return false; } // job now belongs to the dispatcher job.release(); return true; } } // just to pacify compilers return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief handle the http response of the handler //////////////////////////////////////////////////////////////////////////////// void HttpServer::handleResponse (HttpCommTask* task, HttpHandler* handler) { HttpResponse* response = handler->getResponse(); if (response == nullptr) { basics::Exception err(TRI_ERROR_INTERNAL, "no response received from handler", __FILE__, __LINE__); handler->handleError(err); response = handler->getResponse(); } RequestStatisticsAgentSetRequestEnd(handler); handler->RequestStatisticsAgent::transfer(task); if (response != nullptr) { task->handleResponse(response); } else { LOG_ERROR("cannot get any response"); } } // ----------------------------------------------------------------------------- // --SECTION-- protected methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief opens a listen port //////////////////////////////////////////////////////////////////////////////// bool HttpServer::openEndpoint (Endpoint* endpoint) { ListenTask* task = new HttpListenTask(this, endpoint); // ................................................................... // For some reason we have failed in our endeavour to bind to the socket - // this effectively terminates the server // ................................................................... if (! task->isBound()) { deleteTask(task); return false; } _scheduler->registerTask(task); _listenTasks.emplace_back(task); return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief handle request directly //////////////////////////////////////////////////////////////////////////////// HttpHandler::status_t HttpServer::handleRequestDirectly (HttpCommTask* task, HttpHandler* handler) { HttpHandler::status_t status(HttpHandler::HANDLER_FAILED); RequestStatisticsAgentSetRequestStart(handler); try { handler->prepareExecute(); try { status = handler->execute(); } catch (const basics::Exception& ex) { RequestStatisticsAgentSetExecuteError(handler); handler->handleError(ex); } catch (std::exception const& ex) { RequestStatisticsAgentSetExecuteError(handler); basics::Exception err(TRI_ERROR_SYS_ERROR, ex.what(), __FILE__, __LINE__); handler->handleError(err); } catch (...) { RequestStatisticsAgentSetExecuteError(handler); basics::Exception err(TRI_ERROR_SYS_ERROR, __FILE__, __LINE__); handler->handleError(err); } handler->finalizeExecute(); if (status.status == HttpHandler::HANDLER_REQUEUE) { handler->RequestStatisticsAgent::transfer(task); return status; } handleResponse(task, handler); } catch (basics::Exception const& ex) { RequestStatisticsAgentSetExecuteError(handler); LOG_ERROR("caught exception: %s", DIAGNOSTIC_INFORMATION(ex)); } catch (std::exception const& ex) { RequestStatisticsAgentSetExecuteError(handler); LOG_ERROR("caught exception: %s", ex.what()); } catch (...) { RequestStatisticsAgentSetExecuteError(handler); LOG_ERROR("caught exception"); } return status; } //////////////////////////////////////////////////////////////////////////////// /// @brief shuts down a handler for a task //////////////////////////////////////////////////////////////////////////////// void HttpServer::shutdownHandlerByTask (Task* task) { auto commTask = dynamic_cast(task); TRI_ASSERT(commTask != nullptr); auto job = commTask->job(); commTask->clearCurrentJob(); if (job != nullptr) { job->beginShutdown(); } } // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: