ClickHouse/dbms/src/Server/HTTPHandler.cpp

292 lines
9.9 KiB
C++
Raw Normal View History

2012-03-09 04:45:27 +00:00
#include <iomanip>
#include <Poco/Net/HTTPBasicCredentials.h>
2012-03-09 03:06:09 +00:00
2012-03-09 04:45:27 +00:00
#include <statdaemons/Stopwatch.h>
2012-03-09 03:06:09 +00:00
#include <DB/Core/ErrorCodes.h>
#include <DB/IO/ReadBufferFromIStream.h>
2012-03-09 06:36:29 +00:00
#include <DB/IO/ReadBufferFromString.h>
#include <DB/IO/ConcatReadBuffer.h>
2012-03-09 15:46:52 +00:00
#include <DB/IO/CompressedReadBuffer.h>
#include <DB/IO/CompressedWriteBuffer.h>
2012-03-09 03:06:09 +00:00
#include <DB/IO/WriteBufferFromString.h>
#include <DB/IO/WriteHelpers.h>
2012-03-09 04:45:27 +00:00
#include <DB/DataStreams/IProfilingBlockInputStream.h>
2012-03-09 03:06:09 +00:00
#include <DB/Interpreters/executeQuery.h>
#include <DB/Interpreters/Quota.h>
2012-03-09 03:06:09 +00:00
#include <DB/Common/ExternalTable.h>
2012-03-09 15:46:52 +00:00
#include "HTTPHandler.h"
2012-03-09 03:06:09 +00:00
namespace DB
{
void HTTPHandler::processQuery(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response, Output & used_output)
2012-03-09 03:06:09 +00:00
{
LOG_TRACE(log, "Request URI: " << request.getURI());
HTMLForm params(request);
std::istream & istr = request.stream();
bool readonly = request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_GET;
2012-03-09 03:06:09 +00:00
BlockInputStreamPtr query_plan;
/** Часть запроса может быть передана в параметре query, а часть - POST-ом
* (точнее - в теле запроса, а метод не обязательно должен быть POST).
2012-03-09 06:36:29 +00:00
* В таком случае, считается, что запрос - параметр query, затем перевод строки, а затем - данные POST-а.
*/
std::string query_param = params.get("query", "");
if (!query_param.empty())
query_param += '\n';
2012-03-09 15:46:52 +00:00
/// Если указано compress, то будем сжимать результат.
used_output.out = new WriteBufferFromHTTPServerResponse(response);
2012-03-09 15:46:52 +00:00
2013-06-21 21:05:16 +00:00
if (parse<bool>(params.get("compress", "0")))
used_output.out_maybe_compressed = new CompressedWriteBuffer(*used_output.out);
2012-03-09 15:46:52 +00:00
else
used_output.out_maybe_compressed = used_output.out;
/// Имя пользователя и пароль могут быть заданы как в параметрах URL, так и с помощью HTTP Basic authentification (и то, и другое не секъюрно).
std::string user = params.get("user", "default");
std::string password = params.get("password", "");
if (request.hasCredentials())
{
Poco::Net::HTTPBasicCredentials credentials(request);
user = credentials.getUsername();
password = credentials.getPassword();
}
std::string quota_key = params.get("quota_key", "");
std::string query_id = params.get("query_id", "");
2013-09-14 05:14:22 +00:00
Context context = *server.global_context;
context.setGlobalContext(*server.global_context);
2012-03-09 03:06:09 +00:00
context.setUser(user, password, request.clientAddress().host(), quota_key);
context.setCurrentQueryId(query_id);
SharedPtr<ReadBuffer> in_param = new ReadBufferFromString(query_param);
SharedPtr<ReadBuffer> in_post = new ReadBufferFromIStream(istr);
SharedPtr<ReadBuffer> in_post_maybe_compressed;
/// Если указано decompress, то будем разжимать то, что передано POST-ом.
if (parse<bool>(params.get("decompress", "0")))
in_post_maybe_compressed = new CompressedReadBuffer(*in_post);
else
in_post_maybe_compressed = in_post;
SharedPtr<ReadBuffer> in;
2014-04-24 18:54:36 +00:00
if (0 == strncmp(request.getContentType().data(), "multipart/form-data", strlen("multipart/form-data")))
{
in = in_param;
ExternalTablesHandler handler(context, params);
params.load(request, istr, handler);
/// Удаляем уже нененужные параметры из хранилища, чтобы впоследствии не перепутать их с натройками контекста и параметрами запроса.
for (const auto & it : handler.names)
{
params.erase(it + "_format");
params.erase(it + "_types");
params.erase(it + "_structure");
}
}
else
in = new ConcatReadBuffer(*in_param, *in_post_maybe_compressed);
/// Настройки могут быть переопределены в запросе.
for (Poco::Net::NameValueCollection::ConstIterator it = params.begin(); it != params.end(); ++it)
{
if (it->first == "database")
{
context.setCurrentDatabase(it->second);
}
else if (it->first == "default_format")
{
context.setDefaultFormat(it->second);
}
else if (readonly && it->first == "readonly")
{
throw Exception("Setting 'readonly' cannot be overrided in readonly mode", ErrorCodes::READONLY);
}
else if (it->first == "query"
|| it->first == "compress"
|| it->first == "decompress"
|| it->first == "user"
|| it->first == "password"
|| it->first == "quota_key"
|| it->first == "query_id")
{
}
else /// Все неизвестные параметры запроса рассматриваются, как настройки.
context.setSetting(it->first, it->second);
}
2012-08-02 17:33:31 +00:00
if (readonly)
context.getSettingsRef().limits.readonly = true;
2012-03-09 03:56:12 +00:00
context.setInterface(Context::Interface::HTTP);
Context::HTTPMethod http_method = Context::HTTPMethod::UNKNOWN;
if (request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_GET)
http_method = Context::HTTPMethod::GET;
else if (request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_POST)
http_method = Context::HTTPMethod::POST;
context.setHTTPMethod(http_method);
2012-03-09 04:45:27 +00:00
Stopwatch watch;
executeQuery(*in, *used_output.out_maybe_compressed, context, query_plan);
2012-03-09 04:45:27 +00:00
watch.stop();
2012-03-09 03:06:09 +00:00
if (query_plan)
{
std::stringstream log_str;
2012-06-25 05:07:34 +00:00
log_str << "Query pipeline:\n";
2012-03-09 03:06:09 +00:00
query_plan->dumpTree(log_str);
LOG_DEBUG(log, log_str.str());
2012-03-09 04:45:27 +00:00
/// Выведем информацию о том, сколько считано строк и байт.
size_t rows = 0;
size_t bytes = 0;
2012-08-23 23:49:28 +00:00
query_plan->getLeafRowsBytes(rows, bytes);
2012-03-09 04:45:27 +00:00
if (rows != 0)
{
LOG_INFO(log, std::fixed << std::setprecision(3)
2012-07-23 06:19:17 +00:00
<< "Read " << rows << " rows, " << bytes / 1048576.0 << " MiB in " << watch.elapsedSeconds() << " sec., "
<< static_cast<size_t>(rows / watch.elapsedSeconds()) << " rows/sec., " << bytes / 1048576.0 / watch.elapsedSeconds() << " MiB/sec.");
2012-03-09 04:45:27 +00:00
}
2012-03-09 03:06:09 +00:00
}
2013-08-28 20:47:22 +00:00
QuotaForIntervals & quota = context.getQuota();
if (!quota.empty())
LOG_INFO(log, "Quota:\n" << quota.toString());
/// Если не было эксепшена и данные ещё не отправлены - отправляются HTTP заголовки с кодом 200.
used_output.out->finalize();
2012-03-09 03:06:09 +00:00
}
void HTTPHandler::trySendExceptionToClient(std::stringstream & s,
Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response,
Output & used_output)
2012-03-09 03:06:09 +00:00
{
try
2012-06-25 05:07:34 +00:00
{
/** Если POST и Keep-Alive, прочитаем тело до конца.
* Иначе вместо следующего запроса, будет прочитан кусок этого тела.
*/
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST
&& response.getKeepAlive()
&& !request.stream().eof())
{
request.stream().ignore(std::numeric_limits<std::streamsize>::max());
}
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
if (!response.sent() && !used_output.out_maybe_compressed)
{
/// Ещё ничего не отправляли, и даже не знаем, нужно ли сжимать ответ.
response.send() << s.str() << std::endl;
}
else if (used_output.out_maybe_compressed)
{
/** Отправим в использованный (возможно сжатый) поток сообщение об ошибке.
* Сообщение об ошибке может идти невпопад - после каких-то данных.
* Также стоит иметь ввиду, что мы могли уже отправить код 200.
*/
/** Если данные есть в буфере, но их ещё не отправили, то и не будем отправлять */
if (used_output.out->count() - used_output.out->offset() == 0)
{
used_output.out_maybe_compressed->position() = used_output.out_maybe_compressed->buffer().begin();
used_output.out->position() = used_output.out->buffer().begin();
}
std::string exception_message = s.str();
writeString(exception_message, *used_output.out_maybe_compressed);
writeChar('\n', *used_output.out_maybe_compressed);
used_output.out_maybe_compressed->next();
used_output.out->finalize();
}
2012-06-25 05:07:34 +00:00
}
catch (...)
{
LOG_ERROR(log, "Cannot send exception to client");
}
}
2012-06-25 05:07:34 +00:00
2013-08-13 18:59:51 +00:00
void HTTPHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
{
Output used_output;
2012-03-09 03:06:09 +00:00
try
{
bool is_browser = false;
if (request.has("Accept"))
{
String accept = request.get("Accept");
if (0 == strncmp(accept.c_str(), "text/html", strlen("text/html")))
is_browser = true;
}
if (is_browser)
response.setContentType("text/plain; charset=UTF-8");
/// Для того, чтобы работал keep-alive.
if (request.getVersion() == Poco::Net::HTTPServerRequest::HTTP_1_1)
response.setChunkedTransferEncoding(true);
processQuery(request, response, used_output);
2012-03-09 03:06:09 +00:00
LOG_INFO(log, "Done processing query");
}
catch (Exception & e)
{
std::stringstream s;
s << "Code: " << e.code() << ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what()
<< ", Stack trace:\n\n" << e.getStackTrace().toString();
LOG_ERROR(log, s.str());
trySendExceptionToClient(s, request, response, used_output);
}
2012-03-09 03:06:09 +00:00
catch (Poco::Exception & e)
{
std::stringstream s;
s << "Code: " << ErrorCodes::POCO_EXCEPTION << ", e.code() = " << e.code()
<< ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what();
LOG_ERROR(log, s.str());
trySendExceptionToClient(s, request, response, used_output);
2012-03-09 03:06:09 +00:00
}
catch (std::exception & e)
{
std::stringstream s;
s << "Code: " << ErrorCodes::STD_EXCEPTION << ". " << e.what();
LOG_ERROR(log, s.str());
trySendExceptionToClient(s, request, response, used_output);
2012-03-09 03:06:09 +00:00
}
catch (...)
{
std::stringstream s;
s << "Code: " << ErrorCodes::UNKNOWN_EXCEPTION << ". Unknown exception.";
LOG_ERROR(log, s.str());
trySendExceptionToClient(s, request, response, used_output);
2012-03-09 03:06:09 +00:00
}
}
}