2012-03-09 04:45:27 +00:00
|
|
|
#include <iomanip>
|
2016-06-25 07:22:12 +00:00
|
|
|
|
2013-08-10 07:46:45 +00:00
|
|
|
#include <Poco/Net/HTTPBasicCredentials.h>
|
2012-03-09 03:06:09 +00:00
|
|
|
|
2017-02-06 08:23:02 +00:00
|
|
|
#include <DB/Common/ExternalTable.h>
|
2016-07-14 05:22:09 +00:00
|
|
|
#include <DB/Common/StringUtils.h>
|
2012-03-09 04:45:27 +00:00
|
|
|
|
2012-03-09 03:06:09 +00:00
|
|
|
#include <DB/IO/ReadBufferFromIStream.h>
|
2017-01-07 16:11:30 +00:00
|
|
|
#include <DB/IO/ZlibInflatingReadBuffer.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>
|
2017-01-22 15:03:55 +00:00
|
|
|
#include <DB/IO/WriteBufferFromHTTPServerResponse.h>
|
2012-03-09 03:06:09 +00:00
|
|
|
#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>
|
2015-05-29 21:37:17 +00:00
|
|
|
#include <DB/Interpreters/Quota.h>
|
2012-03-09 03:06:09 +00:00
|
|
|
|
2012-03-09 15:46:52 +00:00
|
|
|
#include "HTTPHandler.h"
|
|
|
|
|
2012-03-09 03:06:09 +00:00
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
2016-01-11 21:46:36 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int READONLY;
|
2016-02-12 03:32:05 +00:00
|
|
|
extern const int UNKNOWN_COMPRESSION_METHOD;
|
2017-01-17 17:21:48 +00:00
|
|
|
|
2017-02-06 08:23:02 +00:00
|
|
|
extern const int CANNOT_PARSE_TEXT;
|
|
|
|
extern const int CANNOT_PARSE_ESCAPE_SEQUENCE;
|
|
|
|
extern const int CANNOT_PARSE_QUOTED_STRING;
|
|
|
|
extern const int CANNOT_PARSE_DATE;
|
|
|
|
extern const int CANNOT_PARSE_DATETIME;
|
|
|
|
extern const int CANNOT_PARSE_NUMBER;
|
|
|
|
|
|
|
|
extern const int UNKNOWN_ELEMENT_IN_AST;
|
|
|
|
extern const int UNKNOWN_TYPE_OF_AST_NODE;
|
|
|
|
extern const int TOO_DEEP_AST;
|
|
|
|
extern const int TOO_BIG_AST;
|
|
|
|
extern const int UNEXPECTED_AST_STRUCTURE;
|
|
|
|
|
|
|
|
extern const int UNKNOWN_TABLE;
|
|
|
|
extern const int UNKNOWN_FUNCTION;
|
|
|
|
extern const int UNKNOWN_IDENTIFIER;
|
|
|
|
extern const int UNKNOWN_TYPE;
|
|
|
|
extern const int UNKNOWN_STORAGE;
|
|
|
|
extern const int UNKNOWN_DATABASE;
|
|
|
|
extern const int UNKNOWN_SETTING;
|
|
|
|
extern const int UNKNOWN_DIRECTION_OF_SORTING;
|
|
|
|
extern const int UNKNOWN_AGGREGATE_FUNCTION;
|
|
|
|
extern const int UNKNOWN_FORMAT;
|
|
|
|
extern const int UNKNOWN_DATABASE_ENGINE;
|
|
|
|
extern const int UNKNOWN_TYPE_OF_QUERY;
|
|
|
|
|
|
|
|
extern const int QUERY_IS_TOO_LARGE;
|
|
|
|
|
|
|
|
extern const int NOT_IMPLEMENTED;
|
|
|
|
extern const int SOCKET_TIMEOUT;
|
|
|
|
|
2017-01-17 17:21:48 +00:00
|
|
|
extern const int UNKNOWN_USER;
|
|
|
|
extern const int WRONG_PASSWORD;
|
|
|
|
extern const int REQUIRED_PASSWORD;
|
2016-01-11 21:46:36 +00:00
|
|
|
}
|
|
|
|
|
2017-02-07 05:21:31 +00:00
|
|
|
static Poco::Net::HTTPResponse::HTTPStatus exceptionCodeToHTTPStatus(int exception_code)
|
2017-02-06 08:23:02 +00:00
|
|
|
{
|
|
|
|
using namespace Poco::Net;
|
|
|
|
|
|
|
|
if (exception_code == ErrorCodes::REQUIRED_PASSWORD)
|
|
|
|
return HTTPResponse::HTTP_UNAUTHORIZED;
|
|
|
|
else if (exception_code == ErrorCodes::CANNOT_PARSE_TEXT ||
|
|
|
|
exception_code == ErrorCodes::CANNOT_PARSE_ESCAPE_SEQUENCE ||
|
|
|
|
exception_code == ErrorCodes::CANNOT_PARSE_QUOTED_STRING ||
|
|
|
|
exception_code == ErrorCodes::CANNOT_PARSE_DATE ||
|
|
|
|
exception_code == ErrorCodes::CANNOT_PARSE_DATETIME ||
|
|
|
|
exception_code == ErrorCodes::CANNOT_PARSE_NUMBER)
|
|
|
|
return HTTPResponse::HTTP_BAD_REQUEST;
|
|
|
|
else if (exception_code == ErrorCodes::UNKNOWN_ELEMENT_IN_AST ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_TYPE_OF_AST_NODE ||
|
|
|
|
exception_code == ErrorCodes::TOO_DEEP_AST ||
|
|
|
|
exception_code == ErrorCodes::TOO_BIG_AST ||
|
|
|
|
exception_code == ErrorCodes::UNEXPECTED_AST_STRUCTURE)
|
|
|
|
return HTTPResponse::HTTP_BAD_REQUEST;
|
|
|
|
else if (exception_code == ErrorCodes::UNKNOWN_TABLE ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_FUNCTION ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_IDENTIFIER ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_TYPE ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_STORAGE ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_DATABASE ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_SETTING ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_DIRECTION_OF_SORTING ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_FORMAT ||
|
|
|
|
exception_code == ErrorCodes::UNKNOWN_DATABASE_ENGINE)
|
|
|
|
return HTTPResponse::HTTP_NOT_FOUND;
|
|
|
|
else if (exception_code == ErrorCodes::UNKNOWN_TYPE_OF_QUERY)
|
|
|
|
return HTTPResponse::HTTP_NOT_FOUND;
|
|
|
|
else if (exception_code == ErrorCodes::QUERY_IS_TOO_LARGE)
|
|
|
|
return HTTPResponse::HTTP_REQUESTENTITYTOOLARGE;
|
|
|
|
else if (exception_code == ErrorCodes::NOT_IMPLEMENTED)
|
|
|
|
return HTTPResponse::HTTP_NOT_IMPLEMENTED;
|
|
|
|
else if (exception_code == ErrorCodes::SOCKET_TIMEOUT)
|
|
|
|
return HTTPResponse::HTTP_SERVICE_UNAVAILABLE;
|
|
|
|
|
|
|
|
return HTTPResponse::HTTP_INTERNAL_SERVER_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
HTTPHandler::HTTPHandler(Server & server_)
|
|
|
|
: server(server_)
|
|
|
|
, log(&Logger::get("HTTPHandler"))
|
|
|
|
{
|
|
|
|
}
|
2016-01-11 21:46:36 +00:00
|
|
|
|
2016-06-25 07:22:12 +00:00
|
|
|
void HTTPHandler::processQuery(
|
|
|
|
Poco::Net::HTTPServerRequest & request,
|
|
|
|
HTMLForm & params,
|
|
|
|
Poco::Net::HTTPServerResponse & response,
|
|
|
|
Output & used_output)
|
2012-03-09 03:06:09 +00:00
|
|
|
{
|
2013-08-10 07:46:45 +00:00
|
|
|
LOG_TRACE(log, "Request URI: " << request.getURI());
|
|
|
|
|
|
|
|
std::istream & istr = request.stream();
|
2016-02-12 00:56:15 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Part of the query can be passed in the 'query' parameter and the rest in the request body
|
|
|
|
/// (http method need not necessarily be POST). In this case the entire query consists of the
|
|
|
|
/// contents of the 'query' parameter, a line break and the request body.
|
2012-03-09 06:36:29 +00:00
|
|
|
std::string query_param = params.get("query", "");
|
|
|
|
if (!query_param.empty())
|
|
|
|
query_param += '\n';
|
2014-07-16 01:13:07 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
|
|
|
|
/// The client can pass a HTTP header indicating supported compression method (gzip or deflate).
|
2016-02-12 00:56:15 +00:00
|
|
|
String http_response_compression_methods = request.get("Accept-Encoding", "");
|
2016-02-19 19:02:20 +00:00
|
|
|
bool client_supports_http_compression = false;
|
2017-01-07 16:11:30 +00:00
|
|
|
ZlibCompressionMethod http_response_compression_method {};
|
2016-02-11 21:40:51 +00:00
|
|
|
|
2016-02-12 00:56:15 +00:00
|
|
|
if (!http_response_compression_methods.empty())
|
2016-02-11 21:40:51 +00:00
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Both gzip and deflate are supported. If the client supports both, gzip is preferred.
|
|
|
|
/// NOTE parsing of the list of methods is slightly incorrect.
|
2016-02-12 00:56:15 +00:00
|
|
|
if (std::string::npos != http_response_compression_methods.find("gzip"))
|
2016-02-11 21:40:51 +00:00
|
|
|
{
|
2016-02-19 19:02:20 +00:00
|
|
|
client_supports_http_compression = true;
|
2017-01-07 16:11:30 +00:00
|
|
|
http_response_compression_method = ZlibCompressionMethod::Gzip;
|
2016-02-11 21:40:51 +00:00
|
|
|
}
|
2016-02-12 00:56:15 +00:00
|
|
|
else if (std::string::npos != http_response_compression_methods.find("deflate"))
|
2016-02-11 21:40:51 +00:00
|
|
|
{
|
2016-02-19 19:02:20 +00:00
|
|
|
client_supports_http_compression = true;
|
2017-01-07 16:11:30 +00:00
|
|
|
http_response_compression_method = ZlibCompressionMethod::Zlib;
|
2016-02-11 21:40:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-28 14:14:18 +00:00
|
|
|
used_output.out = std::make_shared<WriteBufferFromHTTPServerResponse>(
|
|
|
|
response, client_supports_http_compression, http_response_compression_method);
|
2016-02-11 21:40:51 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Client can pass a 'compress' flag in the query string. In this case the query result is
|
|
|
|
/// compressed using internal algorithm. This is not reflected in HTTP headers.
|
2013-06-21 21:05:16 +00:00
|
|
|
if (parse<bool>(params.get("compress", "0")))
|
2016-05-28 14:14:18 +00:00
|
|
|
used_output.out_maybe_compressed = std::make_shared<CompressedWriteBuffer>(*used_output.out);
|
2012-03-09 15:46:52 +00:00
|
|
|
else
|
2014-08-04 19:48:50 +00:00
|
|
|
used_output.out_maybe_compressed = used_output.out;
|
2013-08-10 07:46:45 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// User name and password can be passed using query parameters or using HTTP Basic auth (both methods are insecure).
|
2016-12-09 04:20:29 +00:00
|
|
|
/// The user and password can be passed by headers (similar to X-Auth-*), which is used by load balancers to pass authentication information
|
|
|
|
std::string user = request.get("X-ClickHouse-User", params.get("user", "default"));
|
|
|
|
std::string password = request.get("X-ClickHouse-Key", params.get("password", ""));
|
2013-08-10 07:46:45 +00:00
|
|
|
|
|
|
|
if (request.hasCredentials())
|
|
|
|
{
|
|
|
|
Poco::Net::HTTPBasicCredentials credentials(request);
|
|
|
|
|
|
|
|
user = credentials.getUsername();
|
|
|
|
password = credentials.getPassword();
|
|
|
|
}
|
2013-08-12 00:36:18 +00:00
|
|
|
|
2016-12-09 04:20:29 +00:00
|
|
|
std::string quota_key = request.get("X-ClickHouse-Quota", params.get("quota_key", ""));
|
2014-05-16 01:25:20 +00:00
|
|
|
std::string query_id = params.get("query_id", "");
|
2014-07-16 01:13:07 +00:00
|
|
|
|
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
|
|
|
|
2016-10-24 21:40:39 +00:00
|
|
|
context.setUser(user, password, request.clientAddress(), quota_key);
|
2014-02-12 17:31:02 +00:00
|
|
|
context.setCurrentQueryId(query_id);
|
2013-08-10 07:46:45 +00:00
|
|
|
|
2016-05-28 12:22:22 +00:00
|
|
|
std::unique_ptr<ReadBuffer> in_param = std::make_unique<ReadBufferFromString>(query_param);
|
2016-02-12 00:56:15 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
std::unique_ptr<ReadBuffer> in_post_raw = std::make_unique<ReadBufferFromIStream>(istr);
|
2016-02-12 00:56:15 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Request body can be compressed using algorithm specified in the Content-Encoding header.
|
|
|
|
std::unique_ptr<ReadBuffer> in_post;
|
|
|
|
String http_request_compression_method_str = request.get("Content-Encoding", "");
|
2016-02-12 00:56:15 +00:00
|
|
|
if (!http_request_compression_method_str.empty())
|
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
ZlibCompressionMethod method;
|
2016-02-12 00:56:15 +00:00
|
|
|
if (http_request_compression_method_str == "gzip")
|
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
method = ZlibCompressionMethod::Gzip;
|
2016-02-12 00:56:15 +00:00
|
|
|
}
|
|
|
|
else if (http_request_compression_method_str == "deflate")
|
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
method = ZlibCompressionMethod::Zlib;
|
2016-02-12 00:56:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
throw Exception("Unknown Content-Encoding of HTTP request: " + http_request_compression_method_str,
|
|
|
|
ErrorCodes::UNKNOWN_COMPRESSION_METHOD);
|
2017-01-07 16:11:30 +00:00
|
|
|
in_post = std::make_unique<ZlibInflatingReadBuffer>(*in_post_raw, method);
|
2016-02-12 00:56:15 +00:00
|
|
|
}
|
|
|
|
else
|
2017-01-07 16:11:30 +00:00
|
|
|
in_post = std::move(in_post_raw);
|
2016-02-12 00:56:15 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// The data can also be compressed using incompatible internal algorithm. This is indicated by
|
|
|
|
/// 'decompress' query parameter.
|
2016-05-28 12:22:22 +00:00
|
|
|
std::unique_ptr<ReadBuffer> in_post_maybe_compressed;
|
2016-04-09 23:24:38 +00:00
|
|
|
bool in_post_compressed = false;
|
2014-04-08 13:43:20 +00:00
|
|
|
if (parse<bool>(params.get("decompress", "0")))
|
2016-04-09 23:24:38 +00:00
|
|
|
{
|
2016-05-28 12:22:22 +00:00
|
|
|
in_post_maybe_compressed = std::make_unique<CompressedReadBuffer>(*in_post);
|
2016-04-09 23:24:38 +00:00
|
|
|
in_post_compressed = true;
|
|
|
|
}
|
2014-04-08 13:43:20 +00:00
|
|
|
else
|
2016-05-28 12:22:22 +00:00
|
|
|
in_post_maybe_compressed = std::move(in_post);
|
2014-04-08 13:43:20 +00:00
|
|
|
|
2016-05-28 12:22:22 +00:00
|
|
|
std::unique_ptr<ReadBuffer> in;
|
2014-04-24 18:54:36 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Support for "external data for query processing".
|
2016-07-14 05:22:09 +00:00
|
|
|
if (startsWith(request.getContentType().data(), "multipart/form-data"))
|
2014-04-08 13:43:20 +00:00
|
|
|
{
|
2016-05-28 12:22:22 +00:00
|
|
|
in = std::move(in_param);
|
2014-04-08 13:43:20 +00:00
|
|
|
ExternalTablesHandler handler(context, params);
|
|
|
|
|
|
|
|
params.load(request, istr, handler);
|
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Erase unneeded parameters to avoid confusing them later with context settings or query
|
|
|
|
/// parameters.
|
2014-04-08 13:43:20 +00:00
|
|
|
for (const auto & it : handler.names)
|
|
|
|
{
|
2014-04-09 13:32:00 +00:00
|
|
|
params.erase(it + "_format");
|
|
|
|
params.erase(it + "_types");
|
|
|
|
params.erase(it + "_structure");
|
2014-04-08 13:43:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2016-05-28 12:22:22 +00:00
|
|
|
in = std::make_unique<ConcatReadBuffer>(*in_param, *in_post_maybe_compressed);
|
2014-04-08 13:43:20 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Settings can be overridden in the query.
|
|
|
|
/// Some parameters (database, default_format, everything used in the code above) do not
|
|
|
|
/// belong to the Settings class.
|
|
|
|
|
|
|
|
/// 'readonly' setting values mean:
|
|
|
|
/// readonly = 0 - any query is allowed, client can change any setting.
|
|
|
|
/// readonly = 1 - only readonly queries are allowed, client can't change settings.
|
|
|
|
/// readonly = 2 - only readonly queries are allowed, client can change any setting except 'readonly'.
|
|
|
|
|
|
|
|
/// In theory if initially readonly = 0, the client can change any setting and then set readonly
|
|
|
|
/// to some other value.
|
2016-02-12 03:32:05 +00:00
|
|
|
auto & limits = context.getSettingsRef().limits;
|
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Only readonly queries are allowed for HTTP GET requests.
|
2016-02-12 03:32:05 +00:00
|
|
|
if (request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_GET)
|
|
|
|
{
|
|
|
|
if (limits.readonly == 0)
|
|
|
|
limits.readonly = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto readonly_before_query = limits.readonly;
|
|
|
|
|
2013-02-17 19:54:32 +00:00
|
|
|
for (Poco::Net::NameValueCollection::ConstIterator it = params.begin(); it != params.end(); ++it)
|
|
|
|
{
|
|
|
|
if (it->first == "database")
|
|
|
|
{
|
|
|
|
context.setCurrentDatabase(it->second);
|
|
|
|
}
|
2013-06-29 18:03:57 +00:00
|
|
|
else if (it->first == "default_format")
|
|
|
|
{
|
|
|
|
context.setDefaultFormat(it->second);
|
|
|
|
}
|
2013-02-17 19:54:32 +00:00
|
|
|
else if (it->first == "query"
|
|
|
|
|| it->first == "compress"
|
2013-08-10 07:46:45 +00:00
|
|
|
|| it->first == "decompress"
|
|
|
|
|| it->first == "user"
|
2014-05-16 01:25:20 +00:00
|
|
|
|| it->first == "password"
|
|
|
|
|| it->first == "quota_key"
|
2016-06-25 07:22:12 +00:00
|
|
|
|| it->first == "query_id"
|
|
|
|
|| it->first == "stacktrace")
|
2013-02-17 19:54:32 +00:00
|
|
|
{
|
|
|
|
}
|
2016-02-12 03:32:05 +00:00
|
|
|
else
|
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
/// All other query parameters are treated as settings.
|
2016-02-12 03:32:05 +00:00
|
|
|
|
|
|
|
if (readonly_before_query == 1)
|
|
|
|
throw Exception("Cannot override setting (" + it->first + ") in readonly mode", ErrorCodes::READONLY);
|
|
|
|
|
|
|
|
if (readonly_before_query && it->first == "readonly")
|
|
|
|
throw Exception("Setting 'readonly' cannot be overrided in readonly mode", ErrorCodes::READONLY);
|
|
|
|
|
2014-02-13 07:17:22 +00:00
|
|
|
context.setSetting(it->first, it->second);
|
2016-02-12 03:32:05 +00:00
|
|
|
}
|
2013-02-17 19:54:32 +00:00
|
|
|
}
|
2012-08-02 17:33:31 +00:00
|
|
|
|
2017-02-01 01:19:32 +00:00
|
|
|
const Settings & settings = context.getSettingsRef();
|
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// HTTP response compression is turned on only if the client signalled that they support it
|
|
|
|
/// (using Accept-Encoding header) and 'enable_http_compression' setting is turned on.
|
2017-02-01 01:19:32 +00:00
|
|
|
used_output.out->setCompression(client_supports_http_compression && settings.enable_http_compression);
|
2016-02-19 19:02:20 +00:00
|
|
|
if (client_supports_http_compression)
|
2017-02-01 01:19:32 +00:00
|
|
|
used_output.out->setCompressionLevel(settings.http_zlib_compression_level);
|
2016-02-12 02:26:04 +00:00
|
|
|
|
2017-02-01 01:19:32 +00:00
|
|
|
used_output.out->setSendProgressInterval(settings.http_headers_progress_interval_ms);
|
2017-01-22 16:34:17 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// If 'http_native_compression_disable_checksumming_on_decompress' setting is turned on,
|
|
|
|
/// checksums of client data compressed with internal algorithm are not checked.
|
2017-02-01 01:19:32 +00:00
|
|
|
if (in_post_compressed && settings.http_native_compression_disable_checksumming_on_decompress)
|
2016-04-09 23:24:38 +00:00
|
|
|
static_cast<CompressedReadBuffer &>(*in_post_maybe_compressed).disableChecksumming();
|
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Add CORS header if 'add_http_cors_header' setting is turned on and the client passed
|
|
|
|
/// Origin header.
|
2017-02-01 01:19:32 +00:00
|
|
|
used_output.out->addHeaderCORS(settings.add_http_cors_header && !request.get("Origin", "").empty());
|
2016-08-23 18:35:23 +00:00
|
|
|
|
2016-10-24 21:40:39 +00:00
|
|
|
ClientInfo & client_info = context.getClientInfo();
|
|
|
|
client_info.query_kind = ClientInfo::QueryKind::INITIAL_QUERY;
|
|
|
|
client_info.interface = ClientInfo::Interface::HTTP;
|
2015-06-26 20:48:10 +00:00
|
|
|
|
2016-10-24 22:33:40 +00:00
|
|
|
/// Query sent through HTTP interface is initial.
|
|
|
|
client_info.initial_user = client_info.current_user;
|
|
|
|
client_info.initial_query_id = client_info.current_query_id;
|
|
|
|
client_info.initial_address = client_info.current_address;
|
|
|
|
|
2016-10-24 21:40:39 +00:00
|
|
|
ClientInfo::HTTPMethod http_method = ClientInfo::HTTPMethod::UNKNOWN;
|
2015-06-26 20:48:10 +00:00
|
|
|
if (request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_GET)
|
2016-10-24 21:40:39 +00:00
|
|
|
http_method = ClientInfo::HTTPMethod::GET;
|
2015-06-26 20:48:10 +00:00
|
|
|
else if (request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_POST)
|
2016-10-24 21:40:39 +00:00
|
|
|
http_method = ClientInfo::HTTPMethod::POST;
|
2015-06-26 20:48:10 +00:00
|
|
|
|
2016-10-24 21:40:39 +00:00
|
|
|
client_info.http_method = http_method;
|
|
|
|
client_info.http_user_agent = request.get("User-Agent", "");
|
2015-06-26 20:48:10 +00:00
|
|
|
|
2017-01-22 15:03:55 +00:00
|
|
|
/// While still no data has been sent, we will report about query execution progress by sending HTTP headers.
|
2017-02-01 01:19:32 +00:00
|
|
|
if (settings.send_progress_in_http_headers)
|
|
|
|
context.setProgressCallback([&used_output] (const Progress & progress) { used_output.out->onProgress(progress); });
|
2017-01-22 15:03:55 +00:00
|
|
|
|
2017-01-18 13:53:20 +00:00
|
|
|
executeQuery(*in, *used_output.out_maybe_compressed, /* allow_into_outfile = */ false, context,
|
2015-10-30 21:19:54 +00:00
|
|
|
[&response] (const String & content_type) { response.setContentType(content_type); });
|
2012-03-09 03:06:09 +00:00
|
|
|
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Send HTTP headers with code 200 if no exception happened and the data is still not sent to
|
|
|
|
/// the client.
|
2014-08-04 19:48:50 +00:00
|
|
|
used_output.out->finalize();
|
2012-03-09 03:06:09 +00:00
|
|
|
}
|
|
|
|
|
2017-01-17 17:21:48 +00:00
|
|
|
void HTTPHandler::trySendExceptionToClient(const std::string & s, int exception_code,
|
2014-08-04 19:48:50 +00:00
|
|
|
Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response,
|
|
|
|
Output & used_output)
|
2012-03-09 03:06:09 +00:00
|
|
|
{
|
2014-07-14 19:46:06 +00:00
|
|
|
try
|
2012-06-25 05:07:34 +00:00
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
/// If HTTP method is POST and Keep-Alive is turned on, we should read the whole request body
|
|
|
|
/// to avoid reading part of the current request body in the next request.
|
2014-07-16 01:13:07 +00:00
|
|
|
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST
|
|
|
|
&& response.getKeepAlive()
|
|
|
|
&& !request.stream().eof())
|
|
|
|
{
|
|
|
|
request.stream().ignore(std::numeric_limits<std::streamsize>::max());
|
|
|
|
}
|
|
|
|
|
2017-02-06 08:23:02 +00:00
|
|
|
bool auth_fail = exception_code == ErrorCodes::UNKNOWN_USER ||
|
|
|
|
exception_code == ErrorCodes::WRONG_PASSWORD ||
|
|
|
|
exception_code == ErrorCodes::REQUIRED_PASSWORD;
|
2017-01-17 17:21:48 +00:00
|
|
|
|
|
|
|
if (auth_fail)
|
|
|
|
{
|
2017-01-23 19:18:25 +00:00
|
|
|
response.requireAuthentication("ClickHouse server HTTP API");
|
2017-01-17 17:21:48 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-02-07 05:21:31 +00:00
|
|
|
response.setStatusAndReason(exceptionCodeToHTTPStatus(exception_code));
|
2017-01-17 17:21:48 +00:00
|
|
|
}
|
2014-08-04 19:48:50 +00:00
|
|
|
|
|
|
|
if (!response.sent() && !used_output.out_maybe_compressed)
|
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
/// If nothing was sent yet and we don't even know if we must compress the response.
|
2015-06-29 21:35:35 +00:00
|
|
|
response.send() << s << std::endl;
|
2014-08-04 19:48:50 +00:00
|
|
|
}
|
|
|
|
else if (used_output.out_maybe_compressed)
|
|
|
|
{
|
2017-01-07 16:11:30 +00:00
|
|
|
/// Send the error message into already used (and possibly compressed) stream.
|
|
|
|
/// Note that the error message will possibly be sent after some data.
|
|
|
|
/// Also HTTP code 200 could have already been sent.
|
2014-08-04 19:48:50 +00:00
|
|
|
|
2016-06-25 07:22:12 +00:00
|
|
|
/** If buffer has data, and that data wasn't sent yet, then no need to send that data */
|
2014-08-04 20:16:49 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2015-06-29 21:35:35 +00:00
|
|
|
writeString(s, *used_output.out_maybe_compressed);
|
2014-08-04 19:48:50 +00:00
|
|
|
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
|
|
|
}
|
2014-07-14 19:46:06 +00:00
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
LOG_ERROR(log, "Cannot send exception to client");
|
|
|
|
}
|
|
|
|
}
|
2012-06-25 05:07:34 +00:00
|
|
|
|
2017-01-17 17:21:48 +00:00
|
|
|
|
2014-07-14 19:46:06 +00:00
|
|
|
void HTTPHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
|
|
|
|
{
|
2014-08-04 19:48:50 +00:00
|
|
|
Output used_output;
|
|
|
|
|
2016-06-25 07:22:12 +00:00
|
|
|
/// In case of exception, send stack trace to client.
|
|
|
|
bool with_stacktrace = false;
|
|
|
|
|
2012-03-09 03:06:09 +00:00
|
|
|
try
|
|
|
|
{
|
2015-10-30 21:19:54 +00:00
|
|
|
response.setContentType("text/plain; charset=UTF-8");
|
2014-07-14 19:46:06 +00:00
|
|
|
|
2016-06-25 07:22:12 +00:00
|
|
|
/// For keep-alive to work.
|
2014-07-14 19:46:06 +00:00
|
|
|
if (request.getVersion() == Poco::Net::HTTPServerRequest::HTTP_1_1)
|
|
|
|
response.setChunkedTransferEncoding(true);
|
|
|
|
|
2016-06-25 07:22:12 +00:00
|
|
|
HTMLForm params(request);
|
|
|
|
with_stacktrace = parse<bool>(params.get("stacktrace", "0"));
|
|
|
|
|
|
|
|
processQuery(request, params, response, used_output);
|
2012-03-09 03:06:09 +00:00
|
|
|
LOG_INFO(log, "Done processing query");
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
2015-07-01 05:24:08 +00:00
|
|
|
tryLogCurrentException(log);
|
2016-06-25 07:22:12 +00:00
|
|
|
|
|
|
|
std::string exception_message = getCurrentExceptionMessage(with_stacktrace);
|
2017-01-17 17:21:48 +00:00
|
|
|
int exception_code = getCurrentExceptionCode();
|
2016-06-25 07:22:12 +00:00
|
|
|
|
|
|
|
/** If exception is received from remote server, then stack trace is embedded in message.
|
|
|
|
* If exception is thrown on local server, then stack trace is in separate field.
|
|
|
|
*/
|
|
|
|
|
|
|
|
auto embedded_stack_trace_pos = exception_message.find("Stack trace");
|
|
|
|
if (std::string::npos != embedded_stack_trace_pos && !with_stacktrace)
|
|
|
|
exception_message.resize(embedded_stack_trace_pos);
|
|
|
|
|
2017-01-17 17:21:48 +00:00
|
|
|
trySendExceptionToClient(exception_message, exception_code, request, response, used_output);
|
2012-03-09 03:06:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|