diff --git a/CHANGELOG b/CHANGELOG index ffe8f35701..14c99eac49 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,19 +1,58 @@ +v1.1.x (XXXX-XX-XX) +------------------- +* removed startup options "--server.require-keep-alive" and + "--server.secure-require-keep-alive". + The server will now behave as follows which should be more conforming to the + HTTP standard: + * if a client sends a "Connection: close" header, the server will close the + connection + * if a client sends a "Connection: keep-alive" header, the server will not + close the connection + * if a client does not send any "Connection" header, the server will assume + "keep-alive" if the request was an HTTP/1.1 request, and "close" if the + request was an HTTP/1.0 request + +* internal optimisations for HTTP request parsing and response header handling + + +v1.x.x (2012-XX-XX) +------------------- +* changed implementation of TRI_BlockCrc32 algorithm to use 8 bytes at a time + +* fixed Unicode unescaping bugs for \f and surrogate pairs in BasicsC/strings.c + * fixed issue #122: arangod doesn't start if cannot be created * fixed issue #121: wrong collection size reported +* fixed issue #98: Unable to change journalSize + +* fixed issue #88: fds not closed + * fixed escaping of document data in HTML admin front end -* added server startup option --server.disable-admin-interface to turn off the HTML admin interface +* added HTTP basic authentication, use "--server.http-auth yes" to enable -* honor server startup option --database.maximal-journal-size when creating new collections without specific journalsize setting. Previously, these collections were always created with journal file sizes of 32 MB and the --database.maximal-journal-size setting was ignored +* added server startup option --server.disable-admin-interface to turn off the + HTML admin interface + +* honor server startup option --database.maximal-journal-size when creating new + collections without specific journalsize setting. Previously, these + collections were always created with journal file sizes of 32 MB and the + --database.maximal-journal-size setting was ignored + +* added server startup option --database.wait-for-sync to control the default + behavior + +* renamed "--unit-tests" to "--javascript.unit-tests" v1.0.alpha3 (2012-06-30) ------------------------ * fixed issue #116: createCollection=create option doesn't work -* fixed issue #115: Compilation issue under OSX 10.7 Lion & 10.8 Mountain Lion (homebrew) +* fixed issue #115: Compilation issue under OSX 10.7 Lion & 10.8 Mountain Lion + (homebrew) * fixed issue #114: image not found @@ -29,7 +68,8 @@ v1.0.alpha2 (2012-06-24) * fixed issue #103: Should we cleanup the directory structure -* fixed issue #100: "count" attribute exists in cursor response with "count: false" +* fixed issue #100: "count" attribute exists in cursor response with "count: + false" * fixed issue #84 explain command @@ -53,7 +93,8 @@ v1.0.alpha2 (2012-06-24) * fixed several range-related assertion failures in the AQL query optimiser -* fixed AQL query optimisations for some edge cases (e.g. nested subqueries with invalid constant filter expressions) +* fixed AQL query optimisations for some edge cases (e.g. nested subqueries with + invalid constant filter expressions) v1.0.alpha1 (2012-05-28) diff --git a/UnitTests/Cambridge/georeg.cpp b/UnitTests/Cambridge/georeg.cpp index acfb784f88..e8e066ae01 100644 --- a/UnitTests/Cambridge/georeg.cpp +++ b/UnitTests/Cambridge/georeg.cpp @@ -991,7 +991,7 @@ void runTest (int mode) list1 = GeoIndex_NearestCountPoints(gi,&gcp1,222); gcmass(365,list1,222,26557002); - gcp1.latitude = 89,4; + gcp1.latitude = 89.0; gcp1.longitude= 179.9; list1 = GeoIndex_PointsWithinRadius(gi,&gcp1,930000.0); gcmass(367,list1, 1857, 12304881); diff --git a/arangod/BitIndexes/bitarray.c b/arangod/BitIndexes/bitarray.c index 785e0f11ed..264af90eba 100755 --- a/arangod/BitIndexes/bitarray.c +++ b/arangod/BitIndexes/bitarray.c @@ -621,9 +621,9 @@ void printBitarray(TRI_bitarray_t* ba) { // ........................................................................... printf("\n"); - printf("THERE ARE %lu COLUMNS\n",ba->_numColumns); - printf("THE NUMBER OF ALLOCATED BLOCKS IN EACH COLUMN IS %lu\n",ba->_numBlocksInColumn); - printf("THE NUMBER OF THE LAST BLOCK USED IS %lu\n",ba->_lastBlockUsed); + printf("THERE ARE %lu COLUMNS\n", (unsigned long) ba->_numColumns); + printf("THE NUMBER OF ALLOCATED BLOCKS IN EACH COLUMN IS %lu\n", (unsigned long) ba->_numBlocksInColumn); + printf("THE NUMBER OF THE LAST BLOCK USED IS %lu\n", (unsigned long) ba->_lastBlockUsed); printf("\n"); @@ -634,7 +634,7 @@ void printBitarray(TRI_bitarray_t* ba) { } printf("==\n"); for (oo = 0; oo < BITARRAY_MASTER_TABLE_BLOCKSIZE; ++oo) { - printf("ROW %lu: ", ((bb * BITARRAY_MASTER_TABLE_BLOCKSIZE) + oo) ); + printf("ROW %llu: ", (unsigned long long) ((bb * BITARRAY_MASTER_TABLE_BLOCKSIZE) + oo) ); for (j = 0; j < ba->_numColumns; ++j) { BitColumn_t* column; bit_column_int_t* bitInteger; diff --git a/arangod/RestHandler/BatchJob.cpp b/arangod/RestHandler/BatchJob.cpp index fc1f2eddea..32363dfcf8 100644 --- a/arangod/RestHandler/BatchJob.cpp +++ b/arangod/RestHandler/BatchJob.cpp @@ -31,7 +31,7 @@ using namespace triagens::basics; using namespace triagens::rest; - + // ----------------------------------------------------------------------------- // --SECTION-- constructors and destructors // ----------------------------------------------------------------------------- @@ -47,11 +47,12 @@ using namespace triagens::rest; BatchJob::BatchJob (HttpServer* server, HttpHandler* handler) : GeneralServerJob(server, handler), + _doneAccomplisher(NOONE), _handlers(), _subjobs(), _doneLock(), + _setupLock(), _jobsDone(0), - _done(false), _cleanup(false) { } @@ -60,6 +61,7 @@ BatchJob::BatchJob (HttpServer* server, HttpHandler* handler) //////////////////////////////////////////////////////////////////////////////// BatchJob::~BatchJob () { + MUTEX_LOCKER(_setupLock); } //////////////////////////////////////////////////////////////////////////////// @@ -81,11 +83,12 @@ BatchJob::~BatchJob () { void BatchJob::jobDone (BatchSubjob* subjob) { _handler->addResponse(subjob->getHandler()); - _doneLock.lock(); + _doneLock.lock(); ++_jobsDone; if (_jobsDone >= _handlers.size()) { + // all sub-jobs are done if (_cleanup) { _doneLock.unlock(); @@ -93,14 +96,15 @@ void BatchJob::jobDone (BatchSubjob* subjob) { GeneralServerJob::cleanup(); } else { - _done = true; + _doneAccomplisher = ASYNC; _subjobs.clear(); _doneLock.unlock(); - + cleanup(); } } else { + // still something to do _subjobs.erase(subjob); _doneLock.unlock(); } @@ -129,18 +133,22 @@ Job::status_e BatchJob::work () { if (_shutdown != 0) { return Job::JOB_DONE; } + + // we must grab this lock so no one else can kill us while we're iterating + // over the sub handlers + MUTEX_LOCKER(_setupLock); // handler::execute() is called to prepare the batch handler // if it returns anything else but HANDLER_DONE, this is an // indication of an error if (_handler->execute() != Handler::HANDLER_DONE) { // handler failed - _done = true; + _doneAccomplisher = DIRECT; return Job::JOB_FAILED; } - - // handler did not fail + + // setup did not fail bool hasAsync = false; _handlers = _handler->subhandlers(); @@ -152,20 +160,29 @@ Job::status_e BatchJob::work () { executeDirectHandler(handler); } else { - hasAsync = true; + if (!hasAsync) { + // we must do this ourselves. it is not safe to have the dispatcherThread + // call this method because the job might be deleted before that + _handler->setDispatcherThread(0); + hasAsync = true; + } createSubjob(handler); } } - if (hasAsync) { - // we must do this ourselves. it is not safe to have the dispatcherthread - // call this method because the job might be deleted before that - setDispatcherThread(0); - - return Job::JOB_DETACH; + if (!hasAsync) { + // only jobs executed directly, we're done and let the dispatcher kill us + return Job::JOB_DONE; } - return Job::JOB_DONE; + MUTEX_LOCKER(_doneLock); + if (_doneAccomplisher == DIRECT) { + // all jobs already done. last job was finished by direct execution + return Job::JOB_DONE; + } + + // someone else must kill this job + return Job::JOB_DETACH; } //////////////////////////////////////////////////////////////////////////////// @@ -178,7 +195,7 @@ void BatchJob::cleanup () { { MUTEX_LOCKER(_doneLock); - if (_done) { + if (_doneAccomplisher != NOONE) { done = true; } else { @@ -197,24 +214,26 @@ void BatchJob::cleanup () { bool BatchJob::beginShutdown () { LOGGER_TRACE << "shutdown job " << static_cast(this); - - _shutdown = 1; - + + bool cleanup; { - MUTEX_LOCKER(_abandonLock); + MUTEX_LOCKER(_doneLock); + _shutdown = 1; - for (set::iterator i = _subjobs.begin(); i != _subjobs.end(); ++i) { - (*i)->abandon(); + { + MUTEX_LOCKER(_abandonLock); + + for (set::iterator i = _subjobs.begin(); i != _subjobs.end(); ++i) { + (*i)->abandon(); + } } + + _doneAccomplisher = TASK; + cleanup = _cleanup; } - MUTEX_LOCKER(_doneLock); - - if (_cleanup) { - delete this; - } - else { - _done = true; + if (cleanup) { + GeneralServerJob::cleanup(); } return true; @@ -275,12 +294,12 @@ void BatchJob::executeDirectHandler (HttpHandler* handler) { if (status == Handler::HANDLER_DONE) { _handler->addResponse(handler); } - + MUTEX_LOCKER(_doneLock); ++_jobsDone; if (_jobsDone >= _handlers.size()) { - _done = true; + _doneAccomplisher = DIRECT; } } diff --git a/arangod/RestHandler/BatchJob.h b/arangod/RestHandler/BatchJob.h index ed8a6c6a7e..b240f2e9b6 100644 --- a/arangod/RestHandler/BatchJob.h +++ b/arangod/RestHandler/BatchJob.h @@ -60,6 +60,17 @@ namespace triagens { BatchJob (BatchJob const&); BatchJob& operator= (BatchJob const&); +//////////////////////////////////////////////////////////////////////////////// +/// @brief who accomplished the batch job? +//////////////////////////////////////////////////////////////////////////////// + + enum Accomplisher { + NOONE = 0, + TASK = 1, + DIRECT = 2, + ASYNC = 3 + }; + //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// @@ -168,6 +179,12 @@ namespace triagens { protected: +//////////////////////////////////////////////////////////////////////////////// +/// @brief who finalised the batch job? +//////////////////////////////////////////////////////////////////////////////// + + Accomplisher _doneAccomplisher; + //////////////////////////////////////////////////////////////////////////////// /// @brief list of handlers //////////////////////////////////////////////////////////////////////////////// @@ -186,18 +203,18 @@ namespace triagens { triagens::basics::Mutex _doneLock; +//////////////////////////////////////////////////////////////////////////////// +/// @brief setup lock +//////////////////////////////////////////////////////////////////////////////// + + triagens::basics::Mutex _setupLock; + //////////////////////////////////////////////////////////////////////////////// /// @brief number of completed jobs //////////////////////////////////////////////////////////////////////////////// size_t _jobsDone; -//////////////////////////////////////////////////////////////////////////////// -/// @brief done flag -//////////////////////////////////////////////////////////////////////////////// - - bool _done; - //////////////////////////////////////////////////////////////////////////////// /// @brief cleanup seen //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/RestHandler/BatchSubjob.cpp b/arangod/RestHandler/BatchSubjob.cpp index 5626f24b65..fc9eac0354 100644 --- a/arangod/RestHandler/BatchSubjob.cpp +++ b/arangod/RestHandler/BatchSubjob.cpp @@ -74,12 +74,17 @@ BatchSubjob::~BatchSubjob () { //////////////////////////////////////////////////////////////////////////////// void BatchSubjob::cleanup () { + + bool abandon; + { MUTEX_LOCKER(_abandonLock); + abandon = _abandon; + } - if (! _abandon) { - _parent->jobDone(this); - } + if (! abandon) { + // signal the parent (batch job) that a subjob is done + _parent->jobDone(this); } delete this; diff --git a/arangod/RestHandler/RestBatchHandler.cpp b/arangod/RestHandler/RestBatchHandler.cpp index 4d0b72c24a..1b41626ab9 100644 --- a/arangod/RestHandler/RestBatchHandler.cpp +++ b/arangod/RestHandler/RestBatchHandler.cpp @@ -64,6 +64,8 @@ RestBatchHandler::~RestBatchHandler () { // delete protobuf message delete _outputMessages; } + + destroyHandlers(); } //////////////////////////////////////////////////////////////////////////////// @@ -207,22 +209,46 @@ void RestBatchHandler::addResponse (HttpHandler* handler) { void RestBatchHandler::assembleResponse () { assert(_missingResponses == 0); + + size_t messageSize = _outputMessages->ByteSize(); + + // allocate output buffer + char* output = (char*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(char) * messageSize, false); + if (output == NULL) { + generateError(HttpResponse::SERVER_ERROR, + TRI_ERROR_OUT_OF_MEMORY, + "out of memory"); + return; + } _response = new HttpResponse(HttpResponse::OK); _response->setContentType(getContentType()); - string data; - if (!_outputMessages->SerializeToString(&data)) { + // content of message is binary, cannot use null-terminated std::string + if (!_outputMessages->SerializeToArray(output, messageSize)) { + TRI_Free(TRI_UNKNOWN_MEM_ZONE, output); delete _response; generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY, "out of memory"); return; - } - - _response->body().appendText(data); +/* + for (char* x = output; x < output + messageSize; ++x) { + if (*x >= ' ' && *x <= 'z') { + printf("%c", *x); + } + else if (*x == '\n' || *x == '\0') { + printf("\n"); + } + else { + printf("."); + } + } + */ + _response->body().appendText(output, messageSize); + TRI_Free(TRI_UNKNOWN_MEM_ZONE, output); } //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Basics/Dictionary.h b/lib/Basics/Dictionary.h index 429a03ddbf..26e3c8efde 100644 --- a/lib/Basics/Dictionary.h +++ b/lib/Basics/Dictionary.h @@ -239,6 +239,22 @@ namespace triagens { } } +//////////////////////////////////////////////////////////////////////////////// +/// @brief looks up a key +//////////////////////////////////////////////////////////////////////////////// + + KeyValue const* lookup (char const* key, const size_t keyLength) const { + KeyValue l(key, keyLength); + KeyValue const& f = _array.findElement(l); + + if (f._key != 0 && strcmp(f._key, key) == 0) { + return &f; + } + else { + return 0; + } + } + //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/BasicsC/timer.h b/lib/BasicsC/timer.h index 05b6c92e10..eeb193087c 100644 --- a/lib/BasicsC/timer.h +++ b/lib/BasicsC/timer.h @@ -55,6 +55,12 @@ #define TRI_TIMER_GET(name) (TRI_microtime() - TRI_TIMER_NAME(name)) +//////////////////////////////////////////////////////////////////////////////// +/// @brief dump a timer to stdout +//////////////////////////////////////////////////////////////////////////////// + +#define TRI_TIMER_DUMP(name) fprintf(stdout, "timer %s: %f\n", #name, TRI_TIMER_GET(name)); + //////////////////////////////////////////////////////////////////////////////// /// @brief log a timer value to the log in debug mode //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/GeneralServer/GeneralServerDispatcher.h b/lib/GeneralServer/GeneralServerDispatcher.h index c2da521b55..c75a6f57e4 100644 --- a/lib/GeneralServer/GeneralServerDispatcher.h +++ b/lib/GeneralServer/GeneralServerDispatcher.h @@ -295,7 +295,7 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// bool handleRequest (CT * task, GeneralHandler* handler) { - registerHandler(handler, task); + this->registerHandler(handler, task); // execute handler and (possibly) requeue while (true) { @@ -355,7 +355,7 @@ namespace triagens { } //////////////////////////////////////////////////////////////////////////////// -/// @brief shut downs a handler for a task +/// @brief shuts down a handler for a task //////////////////////////////////////////////////////////////////////////////// void shutdownHandlerByTask (Task* task) { diff --git a/lib/GeneralServer/GeneralServerJob.h b/lib/GeneralServer/GeneralServerJob.h index 31c6dc4612..560d3d8761 100644 --- a/lib/GeneralServer/GeneralServerJob.h +++ b/lib/GeneralServer/GeneralServerJob.h @@ -200,12 +200,15 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// void cleanup () { + bool abandon; + { MUTEX_LOCKER(_abandonLock); + abandon = _abandon; + } - if (! _abandon) { - _server->jobDone(this); - } + if (! abandon) { + _server->jobDone(this); } delete this; diff --git a/lib/HttpServer/ApplicationHttpServer.cpp b/lib/HttpServer/ApplicationHttpServer.cpp index 56525f1207..8e30744dde 100644 --- a/lib/HttpServer/ApplicationHttpServer.cpp +++ b/lib/HttpServer/ApplicationHttpServer.cpp @@ -57,7 +57,6 @@ ApplicationHttpServer::ApplicationHttpServer (ApplicationScheduler* applicationS _applicationScheduler(applicationScheduler), _applicationDispatcher(applicationDispatcher), _showPort(true), - _requireKeepAlive(false), _httpServers(), _httpPorts(), _httpAddressPorts() { @@ -168,10 +167,6 @@ void ApplicationHttpServer::setupOptions (map ("server.port", &_httpPorts, "listen port or address:port") ; } - - options[ApplicationServer::OPTIONS_SERVER + ":help-extended"] - ("server.require-keep-alive", "close connection, if keep-alive is missing") - ; } //////////////////////////////////////////////////////////////////////////////// @@ -179,10 +174,6 @@ void ApplicationHttpServer::setupOptions (map //////////////////////////////////////////////////////////////////////////////// bool ApplicationHttpServer::parsePhase2 (ProgramOptions& options) { - if (options.has("server.require-keep-alive")) { - _requireKeepAlive= true; - } - for (vector::const_iterator i = _httpPorts.begin(); i != _httpPorts.end(); ++i) { addPort(*i); } @@ -275,11 +266,6 @@ HttpServer* ApplicationHttpServer::buildHttpServer (HttpServer* httpServer, httpServer = new HttpServer(scheduler, dispatcher); } - // update close-without-keep-alive flag - if (_requireKeepAlive) { - httpServer->setCloseWithoutKeepAlive(true); - } - // keep a list of active server _httpServers.push_back(httpServer); diff --git a/lib/HttpServer/ApplicationHttpServer.h b/lib/HttpServer/ApplicationHttpServer.h index f50b98fa37..5805a6093f 100644 --- a/lib/HttpServer/ApplicationHttpServer.h +++ b/lib/HttpServer/ApplicationHttpServer.h @@ -232,12 +232,6 @@ namespace triagens { bool _showPort; -//////////////////////////////////////////////////////////////////////////////// -/// @brief is keep-alive required to keep the connection open -//////////////////////////////////////////////////////////////////////////////// - - bool _requireKeepAlive; - //////////////////////////////////////////////////////////////////////////////// /// @brief all constructed http servers //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/HttpServer/HttpCommTask.cpp b/lib/HttpServer/HttpCommTask.cpp index 56104bbc8d..a348aac40d 100644 --- a/lib/HttpServer/HttpCommTask.cpp +++ b/lib/HttpServer/HttpCommTask.cpp @@ -235,18 +235,22 @@ bool HttpCommTask::processRead () { _readBuffer->erase_front(_bodyPosition + _bodyLength); _requestPending = true; - - string connectionType = StringUtils::tolower(StringUtils::trim(_request->header("connection"))); + + string connectionType = StringUtils::tolower(_request->header("connection")); if (connectionType == "close") { + // client has sent an explicit "Connection: Close" header. we should close the connection LOGGER_DEBUG << "connection close requested by client"; _closeRequested = true; } - else if (_server->getCloseWithoutKeepAlive() && connectionType != "keep-alive") { + else if (_request->isHttp10() && connectionType != "keep-alive") { + // HTTP 1.0 request, and no "Connection: Keep-Alive" header sent + // we should close the connection LOGGER_DEBUG << "no keep-alive, connection close requested by client"; _closeRequested = true; } - + // we keep the connection open in all other cases (HTTP 1.1 or Keep-Alive header sent) + _readPosition = 0; _bodyPosition = 0; _bodyLength = 0; @@ -286,7 +290,15 @@ bool HttpCommTask::processRead () { void HttpCommTask::addResponse (HttpResponse* response) { StringBuffer * buffer; - + + if (_closeRequested) { + response->setHeader("connection", 10, "Close"); + } + else { + // keep-alive is the default + response->setHeader("connection", 10, "Keep-Alive"); + } + // save header buffer = new StringBuffer(TRI_UNKNOWN_MEM_ZONE); response->writeHeader(buffer); diff --git a/lib/HttpServer/HttpServer.cpp b/lib/HttpServer/HttpServer.cpp index 732092b2ac..e397bbd288 100644 --- a/lib/HttpServer/HttpServer.cpp +++ b/lib/HttpServer/HttpServer.cpp @@ -47,8 +47,7 @@ using namespace triagens::rest; //////////////////////////////////////////////////////////////////////////////// HttpServer::HttpServer (Scheduler* scheduler, Dispatcher* dispatcher) - : GeneralServerDispatcher(scheduler, dispatcher), - _closeWithoutKeepAlive(false) { + : GeneralServerDispatcher(scheduler, dispatcher) { } //////////////////////////////////////////////////////////////////////////////// @@ -80,22 +79,6 @@ Dispatcher* HttpServer::getDispatcher () { return _dispatcher; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief checks if to close connection if keep-alive is missing -//////////////////////////////////////////////////////////////////////////////// - -bool HttpServer::getCloseWithoutKeepAlive () const { - return _closeWithoutKeepAlive; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief close connection if keep-alive is missing -//////////////////////////////////////////////////////////////////////////////// - -void HttpServer::setCloseWithoutKeepAlive (bool value) { - _closeWithoutKeepAlive = value; -} - //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/HttpServer/HttpServer.h b/lib/HttpServer/HttpServer.h index 93843148ee..f503a57c5d 100644 --- a/lib/HttpServer/HttpServer.h +++ b/lib/HttpServer/HttpServer.h @@ -39,7 +39,6 @@ namespace triagens { namespace rest { class HttpHandlerFactory; - class HttpListenTask; // ----------------------------------------------------------------------------- // --SECTION-- class HttpServer @@ -105,38 +104,6 @@ namespace triagens { Dispatcher* getDispatcher (); -//////////////////////////////////////////////////////////////////////////////// -/// @brief checks if to close connection if keep-alive is missing -//////////////////////////////////////////////////////////////////////////////// - - bool getCloseWithoutKeepAlive () const; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief close connection if keep-alive is missing -//////////////////////////////////////////////////////////////////////////////// - - void setCloseWithoutKeepAlive (bool value = true); - -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - -// ----------------------------------------------------------------------------- -// --SECTION-- private variables -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup HttpServer -/// @{ -//////////////////////////////////////////////////////////////////////////////// - - private: - -//////////////////////////////////////////////////////////////////////////////// -/// @brief close connection without explicit keep-alive -//////////////////////////////////////////////////////////////////////////////// - - bool _closeWithoutKeepAlive; }; } } diff --git a/lib/HttpsServer/ApplicationHttpsServer.cpp b/lib/HttpsServer/ApplicationHttpsServer.cpp index 7a29009a39..9b11943fc5 100644 --- a/lib/HttpsServer/ApplicationHttpsServer.cpp +++ b/lib/HttpsServer/ApplicationHttpsServer.cpp @@ -97,7 +97,6 @@ ApplicationHttpsServer::ApplicationHttpsServer (ApplicationScheduler* applicatio _applicationScheduler(applicationScheduler), _applicationDispatcher(applicationDispatcher), _showPort(true), - _requireKeepAlive(false), _sslProtocol(3), _sslCacheMode(0), _sslOptions(SSL_OP_TLS_ROLLBACK_BUG), @@ -197,7 +196,6 @@ void ApplicationHttpsServer::setupOptions (map::const_iterator i = _httpsPorts.begin(); i != _httpsPorts.end(); ++i) { addPort(*i); @@ -320,11 +312,6 @@ HttpsServer* ApplicationHttpsServer::buildHttpsServer (vector const // create new server HttpsServer* httpsServer = new HttpsServer(scheduler, dispatcher, _sslContext); - // update close-without-keep-alive flag - if (_requireKeepAlive) { - httpsServer->setCloseWithoutKeepAlive(true); - } - // keep a list of active server _httpsServers.push_back(httpsServer); diff --git a/lib/HttpsServer/ApplicationHttpsServer.h b/lib/HttpsServer/ApplicationHttpsServer.h index e7a8f20262..63bdbdbdf9 100644 --- a/lib/HttpsServer/ApplicationHttpsServer.h +++ b/lib/HttpsServer/ApplicationHttpsServer.h @@ -230,12 +230,6 @@ namespace triagens { bool _showPort; -//////////////////////////////////////////////////////////////////////////////// -/// @brief is keep-alive required to keep the connection open -//////////////////////////////////////////////////////////////////////////////// - - bool _requireKeepAlive; - //////////////////////////////////////////////////////////////////////////////// /// @brief all constructed http servers //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Rest/HttpRequest.cpp b/lib/Rest/HttpRequest.cpp index 7a05ba4294..445e93f38e 100644 --- a/lib/Rest/HttpRequest.cpp +++ b/lib/Rest/HttpRequest.cpp @@ -57,7 +57,8 @@ HttpRequest::HttpRequest () : _connectionInfo(), _type(HTTP_REQUEST_ILLEGAL), _prefix(), - _suffix() { + _suffix(), + _version(HTTP_1_0) { } //////////////////////////////////////////////////////////////////////////////// @@ -112,6 +113,22 @@ void HttpRequest::setRequestType (HttpRequestType newType) { _type = newType; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns whether HTTP version is 1.0 +//////////////////////////////////////////////////////////////////////////////// + +bool HttpRequest::isHttp10 () { + return _version == HTTP_1_0; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns whether HTTP version is 1.1 +//////////////////////////////////////////////////////////////////////////////// + +bool HttpRequest::isHttp11 () { + return _version == HTTP_1_1; +} + //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Rest/HttpRequest.h b/lib/Rest/HttpRequest.h index b779d28be6..67567b46c4 100644 --- a/lib/Rest/HttpRequest.h +++ b/lib/Rest/HttpRequest.h @@ -87,6 +87,15 @@ namespace triagens { HTTP_REQUEST_ILLEGAL }; +//////////////////////////////////////////////////////////////////////////////// +/// @brief http version +//////////////////////////////////////////////////////////////////////////////// + + enum HttpVersion { + HTTP_1_0, + HTTP_1_1 + }; + //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// @@ -156,6 +165,18 @@ namespace triagens { void setRequestType (HttpRequestType newType); +//////////////////////////////////////////////////////////////////////////////// +/// @brief return whether HTTP version is 1.0 +//////////////////////////////////////////////////////////////////////////////// + + bool isHttp10 (); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return whether HTTP version is 1.1 +//////////////////////////////////////////////////////////////////////////////// + + bool isHttp11 (); + //////////////////////////////////////////////////////////////////////////////// /// @brief returns the full request path /// The request path consists of the URL without the host and without any @@ -379,6 +400,12 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// vector _suffix; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief the HTTP version +//////////////////////////////////////////////////////////////////////////////// + + HttpVersion _version; }; } } diff --git a/lib/Rest/HttpRequestPlain.cpp b/lib/Rest/HttpRequestPlain.cpp index 0c1b178d9a..38144118d5 100644 --- a/lib/Rest/HttpRequestPlain.cpp +++ b/lib/Rest/HttpRequestPlain.cpp @@ -382,6 +382,40 @@ int HttpRequestPlain::setBody (char const* newBody, size_t length) { /// @{ //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// @brief determine the header type +//////////////////////////////////////////////////////////////////////////////// + +HttpRequest::HttpRequestType HttpRequestPlain::getRequestType (const char* ptr, const size_t length) { + switch (length) { + case 3: + if (ptr[0] == 'g' && ptr[1] == 'e' && ptr[2] == 't') { + return HTTP_REQUEST_GET; + } + if (ptr[0] == 'p' && ptr[1] == 'u' && ptr[2] == 't') { + return HTTP_REQUEST_PUT; + } + break; + + case 4: + if (ptr[0] == 'p' && ptr[1] == 'o' && ptr[2] == 's' && ptr[3] == 't') { + return HTTP_REQUEST_POST; + } + if (ptr[0] == 'h' && ptr[1] == 'e' && ptr[2] == 'a' && ptr[3] == 'd') { + return HTTP_REQUEST_HEAD; + } + break; + + case 6: + if (ptr[0] == 'd' && ptr[1] == 'e' && ptr[2] == 'l' && ptr[3] == 'e' && ptr[4] == 't' && ptr[5] == 'e') { + return HTTP_REQUEST_DELETE; + } + break; + } + + return HTTP_REQUEST_ILLEGAL; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief parses the http header //////////////////////////////////////////////////////////////////////////////// @@ -427,6 +461,7 @@ void HttpRequestPlain::parseHeader (char* ptr, size_t length) { // extract the value keyEnd = e; + // trim value from the start while (e < end && *e == ' ') { ++e; } @@ -470,6 +505,32 @@ void HttpRequestPlain::parseHeader (char* ptr, size_t length) { else { valueEnd = e; + // HTTP protocol version is expected next + // trim value + while (e < end && *e == ' ') { + ++e; + } + + if (end - e > strlen("http/1.x")) { + if ((e[0] == 'h' || e[0] == 'H') && + (e[1] == 't' || e[1] == 'T') && + (e[2] == 't' || e[2] == 'T') && + (e[3] == 'p' || e[3] == 'P') && + e[4] == '/' && + e[5] == '1' && + e[6] == '.') { + if (e[7] == '1') { + _version = HTTP_1_1; + } + else { + _version = HTTP_1_0; + } + + e += strlen("http/1.x"); + } + } + + // go on until eol while (e < end && *e != '\n') { ++e; } @@ -487,21 +548,7 @@ void HttpRequestPlain::parseHeader (char* ptr, size_t length) { } // check the key - if (strcmp(keyBegin, "post") == 0) { - _type = HTTP_REQUEST_POST; - } - else if (strcmp(keyBegin, "put") == 0) { - _type = HTTP_REQUEST_PUT; - } - else if (strcmp(keyBegin, "delete") == 0) { - _type = HTTP_REQUEST_DELETE; - } - else if (strcmp(keyBegin, "get") == 0) { - _type = HTTP_REQUEST_GET; - } - else if (strcmp(keyBegin, "head") == 0) { - _type = HTTP_REQUEST_HEAD; - } + _type = getRequestType(keyBegin, keyEnd - keyBegin); // extract the path and decode the url and parameters if (_type != HTTP_REQUEST_ILLEGAL) { @@ -577,21 +624,7 @@ void HttpRequestPlain::parseHeader (char* ptr, size_t length) { } // check the key - if (strcmp(keyBegin, "post") == 0) { - _type = HTTP_REQUEST_POST; - } - else if (strcmp(keyBegin, "put") == 0) { - _type = HTTP_REQUEST_PUT; - } - else if (strcmp(keyBegin, "delete") == 0) { - _type = HTTP_REQUEST_DELETE; - } - else if (strcmp(keyBegin, "get") == 0) { - _type = HTTP_REQUEST_GET; - } - else if (strcmp(keyBegin, "head") == 0) { - _type = HTTP_REQUEST_HEAD; - } + _type = getRequestType(keyBegin, keyEnd - keyBegin); } } diff --git a/lib/Rest/HttpRequestPlain.h b/lib/Rest/HttpRequestPlain.h index 53bcf89494..6be21f4f24 100644 --- a/lib/Rest/HttpRequestPlain.h +++ b/lib/Rest/HttpRequestPlain.h @@ -203,6 +203,12 @@ namespace triagens { private: +//////////////////////////////////////////////////////////////////////////////// +/// @brief determine the header type +//////////////////////////////////////////////////////////////////////////////// + + static HttpRequest::HttpRequestType getRequestType (const char*, const size_t); + //////////////////////////////////////////////////////////////////////////////// /// @brief parses the http header //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Rest/HttpResponse.cpp b/lib/Rest/HttpResponse.cpp index 8cd862ef3a..ee47ee1eb7 100644 --- a/lib/Rest/HttpResponse.cpp +++ b/lib/Rest/HttpResponse.cpp @@ -144,7 +144,7 @@ HttpResponse::HttpResponseCode HttpResponse::responseCode (const string& str) { HttpResponse::HttpResponse () : _code(NOT_IMPLEMENTED), - _headers(5), + _headers(6), _body(TRI_UNKNOWN_MEM_ZONE), _isHeadResponse(false), _bodySize(0), @@ -157,7 +157,7 @@ HttpResponse::HttpResponse () HttpResponse::HttpResponse (string const& header) : _code(NOT_IMPLEMENTED), - _headers(5), + _headers(6), _body(TRI_UNKNOWN_MEM_ZONE), _isHeadResponse(false), _bodySize(0), @@ -171,38 +171,15 @@ HttpResponse::HttpResponse (string const& header) HttpResponse::HttpResponse (HttpResponseCode code) : _code(code), - _headers(5), + _headers(6), _body(TRI_UNKNOWN_MEM_ZONE), _isHeadResponse(false), _bodySize(0), _freeables() { - char* headerBuffer = StringUtils::duplicate("server\ntriagens GmbH High-Performance HTTP Server\n" - "connection\nKeep-Alive\n" - "content-type\ntext/plain;charset=utf-8\n"); - _freeables.push_back(headerBuffer); - - bool key = true; - char* startKey = headerBuffer; - char* startValue = 0; - char* end = headerBuffer + strlen(headerBuffer); - for (char* ptr = headerBuffer; ptr < end; ++ptr) { - if (*ptr == '\n') { - *ptr = '\0'; - - if (key) { - startValue = ptr + 1; - key = false; - } - else { - _headers.insert(startKey, startValue); - - startKey = ptr + 1; - startValue = 0; - key = true; - } - } - } + _headers.insert("server", 6, "triagens GmbH High-Performance HTTP Server"); + _headers.insert("connection", 10, "Keep-Alive"); + _headers.insert("content-type", 12, "text/plain; charset=utf-8"); } //////////////////////////////////////////////////////////////////////////////// @@ -245,7 +222,7 @@ size_t HttpResponse::contentLength () { return _bodySize; } else { - Dictionary::KeyValue const* kv = _headers.lookup("content-length"); + Dictionary::KeyValue const* kv = _headers.lookup("content-length", 14); if (kv == 0) { return 0; @@ -260,7 +237,7 @@ size_t HttpResponse::contentLength () { //////////////////////////////////////////////////////////////////////////////// void HttpResponse::setContentType (string const& contentType) { - setHeader("content-type", contentType); + setHeader("content-type", 12, contentType); } //////////////////////////////////////////////////////////////////////////////// @@ -283,6 +260,21 @@ string HttpResponse::header (string const& key) const { /// @brief returns a header field //////////////////////////////////////////////////////////////////////////////// +string HttpResponse::header (const char* key, const size_t keyLength) const { + Dictionary::KeyValue const* kv = _headers.lookup(key, keyLength); + + if (kv == 0) { + return ""; + } + else { + return kv->_value; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a header field +//////////////////////////////////////////////////////////////////////////////// + string HttpResponse::header (string const& key, bool& found) const { string k = StringUtils::tolower(key); Dictionary::KeyValue const* kv = _headers.lookup(k.c_str()); @@ -297,6 +289,23 @@ string HttpResponse::header (string const& key, bool& found) const { } } +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a header field +//////////////////////////////////////////////////////////////////////////////// + +string HttpResponse::header (const char* key, const size_t keyLength, bool& found) const { + Dictionary::KeyValue const* kv = _headers.lookup(key, keyLength); + + if (kv == 0) { + found = false; + return ""; + } + else { + found = true; + return kv->_value; + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief returns all header fields //////////////////////////////////////////////////////////////////////////////// @@ -324,6 +333,36 @@ map HttpResponse::headers () const { /// @brief sets a header field //////////////////////////////////////////////////////////////////////////////// +void HttpResponse::setHeader (const char* key, const size_t keyLength, string const& value) { + if (value.empty()) { + _headers.erase(key); + } + else { + char const* v = StringUtils::duplicate(value); + + _headers.insert(key, keyLength, v); + + _freeables.push_back(v); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief sets a header field +//////////////////////////////////////////////////////////////////////////////// + +void HttpResponse::setHeader (const char* key, const size_t keyLength, const char* value) { + if (*value == '\0') { + _headers.erase(key); + } + else { + _headers.insert(key, keyLength, value); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief sets a header field +//////////////////////////////////////////////////////////////////////////////// + void HttpResponse::setHeader (string const& key, string const& value) { string lk = StringUtils::tolower(key); @@ -490,11 +529,11 @@ void HttpResponse::writeHeader (StringBuffer* output) { } // ignore content-length - if (strcmp(key, "content-length") == 0) { + if (*key == 'c' && (strcmp(key, "content-length") == 0)) { continue; } - if (strcmp(key, "transfer-encoding") == 0) { + if (*key == 't' && (strcmp(key, "transfer-encoding") == 0)) { seenTransferEncoding = true; transferEncoding = begin->_value; continue; @@ -509,7 +548,7 @@ void HttpResponse::writeHeader (StringBuffer* output) { } if (seenTransferEncoding && transferEncoding == "chunked") { - output->appendText("transfer-encoding: chunked\r\n"); + output->appendText("transfer-encoding: chunked\r\n\r\n"); } else { if (seenTransferEncoding) { @@ -527,10 +566,9 @@ void HttpResponse::writeHeader (StringBuffer* output) { output->appendInteger(_body.length()); } - output->appendText("\r\n"); + output->appendText("\r\n\r\n"); } - - output->appendText("\r\n"); + // end of header, body to follow } //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Rest/HttpResponse.h b/lib/Rest/HttpResponse.h index 9f9e3ba33c..ac783c5048 100644 --- a/lib/Rest/HttpResponse.h +++ b/lib/Rest/HttpResponse.h @@ -238,11 +238,32 @@ namespace triagens { /// /// Returns the value of a header field with given name. If no header field /// with the given name was specified by the client, the empty string is -/// returned. found is try if the client specified the header field. +/// returned. +/// The header field name must already be trimmed and lower-cased +//////////////////////////////////////////////////////////////////////////////// + + string header (const char*, const size_t) const; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a header field +/// +/// Returns the value of a header field with given name. If no header field +/// with the given name was specified by the client, the empty string is +/// returned. found is set if the client specified the header field. //////////////////////////////////////////////////////////////////////////////// string header (string const& field, bool& found) const; +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a header field +/// +/// Returns the value of a header field with given name. If no header field +/// with the given name was specified by the client, the empty string is +/// returned. found is set if the client specified the header field. +//////////////////////////////////////////////////////////////////////////////// + + string header (const char*, const size_t, bool& found) const; + //////////////////////////////////////////////////////////////////////////////// /// @brief returns all header fields /// @@ -254,7 +275,26 @@ namespace triagens { //////////////////////////////////////////////////////////////////////////////// /// @brief sets a header field /// -/// The key is automatically converted to lower case. +/// The key must be lowercased and trimmed already +/// The key string must remain valid until the response is destroyed +//////////////////////////////////////////////////////////////////////////////// + + void setHeader (const char*, const size_t, string const& value); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief sets a header field +/// +/// The key must be lowercased and trimmed already +/// The key string must remain valid until the response is destroyed +/// The value string must remain valid until the response is destroyed +//////////////////////////////////////////////////////////////////////////////// + + void setHeader (const char*, const size_t, const char*); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief sets a header field +/// +/// The key is automatically converted to lower case and trimmed. //////////////////////////////////////////////////////////////////////////////// void setHeader (string const& key, string const& value);