mirror of https://gitee.com/bigwinds/arangodb
526 lines
20 KiB
C++
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:
|