1
0
Fork 0
arangodb/lib/HttpServer/HttpCommTask.h

526 lines
20 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief task for http communication
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2012 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 triAGENS GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
/// @author Achim Brandt
/// @author Copyright 2009-2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#ifndef TRIAGENS_HTTP_SERVER_HTTP_COMM_TASK_H
#define TRIAGENS_HTTP_SERVER_HTTP_COMM_TASK_H 1
#include "GeneralServer/GeneralCommTask.h"
#include "Basics/StringUtils.h"
#include "Basics/StringBuffer.h"
#include "Scheduler/ListenTask.h"
#include "HttpServer/HttpHandler.h"
#include "HttpServer/HttpHandlerFactory.h"
#include "Rest/HttpRequest.h"
#include "Rest/HttpResponse.h"
#include "Scheduler/Scheduler.h"
// -----------------------------------------------------------------------------
// --SECTION-- forward declarations
// -----------------------------------------------------------------------------
namespace triagens {
namespace rest {
// -----------------------------------------------------------------------------
// --SECTION-- class HttpCommTask
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup HttpServer
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief task for http communication
////////////////////////////////////////////////////////////////////////////////
template<typename S>
class HttpCommTask : public GeneralCommTask<S, HttpHandlerFactory>,
public RequestStatisticsAgent {
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- constructors and destructors
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup HttpServer
/// @{
////////////////////////////////////////////////////////////////////////////////
public:
////////////////////////////////////////////////////////////////////////////////
/// @brief constructors
////////////////////////////////////////////////////////////////////////////////
HttpCommTask (S* server,
socket_t fd,
ConnectionInfo const& info,
double keepAliveTimeout)
: Task("HttpCommTask"),
GeneralCommTask<S, HttpHandlerFactory>(server, fd, info, keepAliveTimeout),
_requestType(HttpRequest::HTTP_REQUEST_ILLEGAL) {
ConnectionStatisticsAgentSetHttp(this);
ConnectionStatisticsAgent::release();
ConnectionStatisticsAgent::acquire();
ConnectionStatisticsAgentSetStart(this);
ConnectionStatisticsAgentSetHttp(this);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief destructors
////////////////////////////////////////////////////////////////////////////////
virtual ~HttpCommTask () {
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- GeneralCommTask methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup HttpServer
/// @{
////////////////////////////////////////////////////////////////////////////////
protected:
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
bool processRead () {
if (this->_requestPending || this->_readBuffer->c_str() == 0) {
return true;
}
bool handleRequest = false;
if (! this->_readRequestBody) {
#ifdef TRI_ENABLE_FIGURES
if (this->_readPosition == 0 && this->_readBuffer->c_str() != this->_readBuffer->end()) {
RequestStatisticsAgent::acquire();
RequestStatisticsAgentSetReadStart(this);
}
#endif
const char * ptr = this->_readBuffer->c_str() + this->_readPosition;
const char * end = this->_readBuffer->end() - 3;
for (; ptr < end; ptr++) {
if (ptr[0] == '\r' && ptr[1] == '\n' && ptr[2] == '\r' && ptr[3] == '\n') {
break;
}
}
size_t headerLength = ptr - this->_readBuffer->c_str();
if (headerLength > this->_maximalHeaderSize) {
LOGGER_WARNING << "maximal header size is " << this->_maximalHeaderSize << ", request header size is " << headerLength;
// header is too large
HttpResponse response(HttpResponse::HEADER_TOO_LARGE);
this->handleResponse(&response);
return true;
}
if (ptr < end) {
this->_readPosition = ptr - this->_readBuffer->c_str() + 4;
LOGGER_TRACE << "HTTP READ FOR " << static_cast<Task*>(this) << ":\n"
<< string(this->_readBuffer->c_str(), this->_readPosition);
// check that we know, how to serve this request
this->_request = this->_server->getHandlerFactory()->createRequest(this->_readBuffer->c_str(), this->_readPosition);
if (this->_request == 0) {
LOGGER_ERROR << "cannot generate request";
// internal server error
HttpResponse response(HttpResponse::SERVER_ERROR);
this->handleResponse(&response);
return false;
}
// update the connection information, i. e. client and server addresses and ports
this->_request->setConnectionInfo(this->_connectionInfo);
LOGGER_TRACE << "server port = " << this->_connectionInfo.serverPort << ", client port = " << this->_connectionInfo.clientPort;
// set body start to current position
this->_bodyPosition = this->_readPosition;
// store the original request's type. we need it later when responding
// (original request objects gets deleted before responding)
this->_requestType = this->_request->requestType();
// handle different HTTP methods
switch (this->_requestType) {
case HttpRequest::HTTP_REQUEST_GET:
case HttpRequest::HTTP_REQUEST_DELETE:
case HttpRequest::HTTP_REQUEST_HEAD:
case HttpRequest::HTTP_REQUEST_POST:
case HttpRequest::HTTP_REQUEST_PUT:
case HttpRequest::HTTP_REQUEST_PATCH: {
const bool expectContentLength = (this->_requestType == HttpRequest::HTTP_REQUEST_POST ||
this->_requestType == HttpRequest::HTTP_REQUEST_PUT ||
this->_requestType == HttpRequest::HTTP_REQUEST_PATCH);
if (! checkContentLength(expectContentLength)) {
return true;
}
if (this->_bodyLength == 0) {
handleRequest = true;
}
break;
}
default: {
LOGGER_WARNING << "got corrupted HTTP request '" << string(this->_readBuffer->c_str(), (this->_readPosition < 6 ? this->_readPosition : 6)) << "'";
// bad request, method not allowed
HttpResponse response(HttpResponse::METHOD_NOT_ALLOWED);
this->handleResponse(&response);
return true;
}
}
// check for a 100-continue
if (this->_readRequestBody) {
bool found;
string const& expect = this->_request->header("expect", found);
if (found && triagens::basics::StringUtils::trim(expect) == "100-continue") {
LOGGER_TRACE << "received a 100-continue request";
triagens::basics::StringBuffer* buffer = new triagens::basics::StringBuffer(TRI_UNKNOWN_MEM_ZONE);
buffer->appendText("HTTP/1.1 100 (Continue)\r\n\r\n");
this->_writeBuffers.push_back(buffer);
#ifdef TRI_ENABLE_FIGURES
this->_writeBuffersStats.push_back(0);
#endif
this->fillWriteBuffer();
}
}
}
else {
if (this->_readBuffer->c_str() < end) {
this->_readPosition = end - this->_readBuffer->c_str();
}
}
}
// readRequestBody might have changed, so cannot use else
if (this->_readRequestBody) {
if (this->_bodyLength > this->_maximalBodySize) {
LOGGER_WARNING << "maximal body size is " << this->_maximalBodySize << ", request body size is " << this->_bodyLength;
// request entity too large
HttpResponse response(HttpResponse::ENTITY_TOO_LARGE);
this->handleResponse(&response);
return true;
}
if (this->_readBuffer->length() - this->_bodyPosition < this->_bodyLength) {
// still more data to be read
SocketTask* socketTask = dynamic_cast<SocketTask*>(this);
if (socketTask) {
// set read request time-out
LOGGER_TRACE << "waiting for rest of body to be received. request timeout set to 60 s";
socketTask->setKeepAliveTimeout(60.0);
}
// let client send more
return true;
}
// read "bodyLength" from read buffer and add this body to "httpRequest"
this->_request->setBody(this->_readBuffer->c_str() + this->_bodyPosition, this->_bodyLength);
LOGGER_TRACE << string(this->_readBuffer->c_str() + this->_bodyPosition, this->_bodyLength);
// remove body from read buffer and reset read position
this->_readRequestBody = false;
handleRequest = true;
}
// we have to delete request in here or pass it to a handler, which will delete it
if (handleRequest) {
RequestStatisticsAgentSetReadEnd(this);
RequestStatisticsAgentAddReceivedBytes(this, this->_bodyPosition + this->_bodyLength);
this->_readBuffer->erase_front(this->_bodyPosition + this->_bodyLength);
if (this->_readBuffer->length() > 0) {
// we removed the front of the read buffer, but it still contains data.
// this means that the content-length header of the request must have been wrong
// (value in content-length header smaller than actual body size)
// check if there is invalid stuff left in the readbuffer
// whitespace is allowed
const char* p = this->_readBuffer->begin();
const char* e = this->_readBuffer->end();
while (p < e) {
const char c = *(p++);
if (c != '\n' && c != '\r' && c != ' ' && c != '\t' && c != '\0') {
LOGGER_WARNING << "read buffer is not empty. probably got a wrong Content-Length header?";
HttpResponse response(HttpResponse::BAD);
this->handleResponse(&response);
return true;
}
}
}
this->_requestPending = true;
// .............................................................................
// keep-alive handling
// .............................................................................
string connectionType = triagens::basics::StringUtils::tolower(this->_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";
this->_closeRequested = true;
}
else if (this->_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";
this->_closeRequested = true;
}
else if (this->_keepAliveTimeout <= 0.0) {
// if keepAliveTimeout was set to 0.0, we'll close even keep-alive connections immediately
LOGGER_DEBUG << "keep-alive disabled by admin";
this->_closeRequested = true;
}
// we keep the connection open in all other cases (HTTP 1.1 or Keep-Alive header sent)
this->_readPosition = 0;
this->_bodyPosition = 0;
this->_bodyLength = 0;
// .............................................................................
// authenticate
// .............................................................................
bool auth = this->_server->getHandlerFactory()->authenticateRequest(this->_request);
// authenticated
if (auth) {
HttpHandler* handler = this->_server->getHandlerFactory()->createHandler(this->_request);
bool ok = false;
if (handler == 0) {
LOGGER_TRACE << "no handler is known, giving up";
delete this->_request;
this->_request = 0;
HttpResponse response(HttpResponse::NOT_FOUND);
this->handleResponse(&response);
}
else {
this->RequestStatisticsAgent::transfer(handler);
this->_request = 0;
ok = this->_server->handleRequest(this, handler);
if (! ok) {
HttpResponse response(HttpResponse::SERVER_ERROR);
this->handleResponse(&response);
}
}
}
// not authenticated
else {
string realm = "basic realm=\"" + this->_server->getHandlerFactory()->authenticationRealm(this->_request) + "\"";
delete this->_request;
this->_request = 0;
HttpResponse response(HttpResponse::UNAUTHORIZED);
response.setHeader("www-authenticate", strlen("www-authenticate"), realm.c_str());
this->handleResponse(&response);
}
return processRead();
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
void addResponse (HttpResponse* response) {
if (this->_closeRequested) {
response->setHeader("connection", strlen("connection"), "Close");
}
else {
// keep-alive is the default
response->setHeader("connection", strlen("connection"), "Keep-Alive");
}
if (this->_requestType == HttpRequest::HTTP_REQUEST_HEAD) {
// clear body if this is an HTTP HEAD request
// HEAD must not return a body
response->headResponse(response->bodySize());
}
// reserve some outbuffer size
const size_t len = response->bodySize() + 128;
triagens::basics::StringBuffer* buffer = new triagens::basics::StringBuffer(TRI_UNKNOWN_MEM_ZONE, len);
// write header
response->writeHeader(buffer);
// write body
buffer->appendText(response->body());
this->_writeBuffers.push_back(buffer);
#ifdef TRI_ENABLE_FIGURES
this->_writeBuffersStats.push_back(RequestStatisticsAgent::transfer());
#endif
LOGGER_TRACE << "HTTP WRITE FOR " << static_cast<Task*>(this) << ":\n" << buffer->c_str();
// clear body
response->body().clear();
// start output
this->fillWriteBuffer();
}
////////////////////////////////////////////////////////////////////////////////
/// check the content-length header of a request and fail it is broken
////////////////////////////////////////////////////////////////////////////////
bool checkContentLength (const bool expectContentLength) {
const int64_t bodyLength = this->_request->contentLength();
if (bodyLength < 0) {
// bad request, body length is < 0. this is a client error
HttpResponse response(HttpResponse::LENGTH_REQUIRED);
this->handleResponse(&response);
return false;
}
if (! expectContentLength && bodyLength > 0) {
// content-length header was sent but the request method does not support that
// we'll warn but read the body anyway
LOGGER_WARNING << "received HTTP GET/DELETE/HEAD request with content-length, this should not happen";
}
if ((size_t) bodyLength > this->_maximalBodySize) {
// request entity too large
LOGGER_WARNING << "maximal body size is " << this->_maximalBodySize << ", request body size is " << bodyLength;
HttpResponse response(HttpResponse::ENTITY_TOO_LARGE);
this->handleResponse(&response);
return false;
}
// set instance variable to content-length value
this->_bodyLength = (size_t) bodyLength;
if (this->_bodyLength > 0) {
// we'll read the body
this->_readRequestBody = true;
}
// everything's fine
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- private variables
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup HttpServer
/// @{
////////////////////////////////////////////////////////////////////////////////
private:
HttpRequest::HttpRequestType _requestType;
};
}
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
#endif
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\)"
// End: