mirror of https://gitee.com/bigwinds/arangodb
522 lines
17 KiB
C++
522 lines
17 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// @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<uint64_t, HttpCommTask*> 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<HttpHandler>& handler,
|
|
uint64_t* jobId) {
|
|
// execute the handler using the dispatcher
|
|
std::unique_ptr<HttpServerJob> 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<HttpHandler>& 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<HttpServerJob> 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<HttpCommTask*>(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:
|