blob: 06cae82adbcc92a39ada1ff1c765d954894003fd [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
#pragma once
#include <map>
#include <string>
#include <boost/function.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <rapidjson/fwd.h>
#include "common/status.h"
#include "kudu/util/web_callback_registry.h"
#include "thirdparty/squeasel/squeasel.h"
#include "util/metrics-fwd.h"
#include "util/network-util.h"
#include "util/openssl-util.h"
namespace impala {
/// Supported HTTP content types
enum ContentType {
/// Corresponds to text/html content-type and used for rendering HTML webpages.
HTML,
/// Following two are used for rendering text-only pages and correspond to text/plain
/// and application/json content-types respectively. JSON type additionally pretty
/// prints the underlying JSON content. They are primarily used for debugging and for
/// integration with third-party tools that can consume the text format easily.
PLAIN,
JSON
};
/// Wrapper class for the Squeasel web server library. Clients may register callback
/// methods which produce Json documents which are rendered via a template file to either
/// HTML or text.
///
/// Apache Knox Integration
/// -----------------------
/// In order to be compatible with the Knox 'impalaui' service definition, there are a few
/// requirements that template files served by this webserver have to conform to:
/// - Any relative links to pages on this server must be proceeded by
/// {{ __common__.host-url }} so that they can be made into absolute urls when Knox
/// proxying is being used.
/// - Any forms must contain {{>www/form-hidden-inputs.tmpl}}, which adds some hidden
/// fields when Knox proxying is being used.
/// - Any relative urls that are accessed via javascript should be constructed with the
/// make_url() function in common-header.tmpl.
/// See: https://github.com/apache/knox/tree/master/gateway-service-definitions/
/// src/main/resources/services/impalaui/1.0.0
class Webserver {
public:
using ArgumentMap = kudu::WebCallbackRegistry::ArgumentMap;
using WebRequest = kudu::WebCallbackRegistry::WebRequest;
using HttpStatusCode = kudu::HttpStatusCode;
typedef boost::function<void (const WebRequest& req, rapidjson::Document* json)>
UrlCallback;
typedef boost::function<void (const WebRequest& req, std::stringstream* output,
HttpStatusCode* response)> RawUrlCallback;
/// Any callback may add a member to their Json output with key ENABLE_RAW_HTML_KEY;
/// this causes the result of the template rendering process to be sent to the browser
/// as text, not HTML.
static const char* ENABLE_RAW_HTML_KEY;
/// Any callback may add a member to their Json output with key ENABLE_PLAIN_JSON_KEY;
/// this causes the result of the template rendering process to be sent to the browser
/// as pretty printed JSON plain text.
static const char* ENABLE_PLAIN_JSON_KEY;
/// Using this constructor, the webserver will bind to 'interface', or all available
/// interfaces if not specified.
Webserver(const std::string& interface, const int port, MetricGroup* metrics);
/// Uses FLAGS_webserver_{port, interface}
Webserver(MetricGroup* metrics);
~Webserver();
/// Starts a webserver on the port passed to the constructor. The webserver runs in a
/// separate thread, so this call is non-blocking.
Status Start();
/// Stops the webserver synchronously.
void Stop();
/// Register a callback for a Url that produces a json document that will be rendered
/// with the template at 'template_filename'. The URL 'path' should not include the
/// http://hostname/ prefix. If is_on_nav_bar is true, the page will appear in the
/// standard navigation bar rendered on all pages.
//
/// Only one callback may be registered per URL.
//
/// The path of the template file is relative to the webserver's document
/// root.
void RegisterUrlCallback(const std::string& path, const std::string& template_filename,
const UrlCallback& callback, bool is_on_nav_bar);
/// Register a 'raw' url callback that produces a bytestream as output. This should only
/// be used for URLs that want to return binary data; non-HTML callbacks that want to
/// produce text should use UrlCallback.
void RegisterUrlCallback(const std::string& path, const RawUrlCallback& callback);
/// True if serving all traffic over SSL, false otherwise
bool IsSecure() const;
/// Returns the URL to the webserver as a string.
string url() { return url_; }
string hostname() { return hostname_; }
int port() { return http_address_.port; }
/// Returns the appropriate MIME type for a given ContentType.
static const std::string GetMimeType(const ContentType& content_type);
private:
/// Contains all information relevant to rendering one Url. Each Url has one callback
/// that produces the output to render. The callback either produces a Json document
/// which is rendered via a template file, or it produces an HTML string that is embedded
/// directly into the output.
class UrlHandler {
public:
UrlHandler(const UrlCallback& cb, const std::string& template_filename,
bool is_on_nav_bar)
: is_on_nav_bar_(is_on_nav_bar), use_templates_(true), template_callback_(cb),
template_filename_(template_filename) { }
UrlHandler(const RawUrlCallback& cb)
: is_on_nav_bar_(false), use_templates_(false),
raw_callback_(cb) { }
bool is_on_nav_bar() const { return is_on_nav_bar_; }
bool use_templates() const { return use_templates_; }
const UrlCallback& callback() const { return template_callback_; }
const RawUrlCallback& raw_callback() const { return raw_callback_; }
const std::string& template_filename() const { return template_filename_; }
private:
/// If true, the page appears in the navigation bar.
bool is_on_nav_bar_;
/// If true, use the template rendering callback, otherwise the 'raw' callback is
/// used.
bool use_templates_;
/// Callback to produce a Json document to render via a template.
UrlCallback template_callback_;
/// Callback to produce a raw bytestream.
RawUrlCallback raw_callback_;
/// Path to the file that contains the template to render, relative to the webserver's
/// document root.
std::string template_filename_;
};
/// Sets the values of 'url_' and 'hostname_'.
void Init();
/// Squeasel callback for log events. Returns squeasel success code.
static int LogMessageCallbackStatic(const struct sq_connection* connection,
const char* message);
/// Squeasel callback for HTTP request events. Static so that it can act as a function
/// pointer, and then call the next method. Returns squeasel success code.
static sq_callback_result_t BeginRequestCallbackStatic(
struct sq_connection* connection);
/// Dispatch point for all incoming requests. Returns squeasel success code.
sq_callback_result_t BeginRequestCallback(struct sq_connection* connection,
struct sq_request_info* request_info);
// Handle SPNEGO authentication for this request. Returns SQ_CONTINUE_HANDLING
// if authentication was successful, otherwise responds to the request and
// returns SQ_HANDLED_OK.
sq_callback_result_t HandleSpnego(struct sq_connection* connection,
struct sq_request_info* request_info, std::vector<std::string>* response_headers);
/// Renders URLs through the Mustache templating library.
/// - Default ContentType is HTML.
/// - Argument 'raw' renders the page with PLAIN ContentType.
/// - Argument 'json' renders the page with JSON ContentType. The underlying JSON is
/// pretty-printed.
void RenderUrlWithTemplate(const struct sq_connection* connection,
const WebRequest& arguments, const UrlHandler& url_handler,
std::stringstream* output, ContentType* content_type);
/// Called when an error is encountered, e.g. when a handler for a URI cannot be found.
void ErrorHandler(const WebRequest& req, rapidjson::Document* document);
/// Builds a map of argument name to argument value from a typical URL argument
/// string (that is, "key1=value1&key2=value2.."). If no value is given for a
/// key, it is entered into the map as (key, "").
void BuildArgumentMap(const std::string& args, ArgumentMap* output);
/// Adds a __common__ object to document with common data that every webpage might want
/// to read (e.g. the names of links to write to the navbar).
void GetCommonJson(rapidjson::Document* document,
const struct sq_connection* connection, const WebRequest& req);
/// Lock guarding the path_handlers_ map
boost::shared_mutex url_handlers_lock_;
/// Map of path to a UrlHandler containing a list of handlers for that
/// path. More than one handler may register itself with a path so that many
/// components may contribute to a single page.
typedef std::map<std::string, UrlHandler> UrlHandlerMap;
UrlHandlerMap url_handlers_;
/// The address of the interface on which to run this webserver.
TNetworkAddress http_address_;
/// Formatted string representing 'http_address_'.
std::string url_;
/// The resolved hostname from 'http_address_'.
std::string hostname_;
/// Handle to Squeasel context; owned and freed by Squeasel internally
struct sq_context* context_;
/// Catch-all handler for error messages
UrlHandler error_handler_;
/// Used to generate and verify signatures for cookies.
AuthenticationHash hash_;
/// If true and SPNEGO is in use, cookies will be used for authentication.
bool use_cookies_;
/// If 'FLAGS_webserver_require_spnego' is true, metrics for the number of successful
/// and failed Negotiate auth attempts.
IntCounter* total_negotiate_auth_success_ = nullptr;
IntCounter* total_negotiate_auth_failure_ = nullptr;
/// If 'use_cookies_' is true, metrics for the number of successful and failed cookie
/// auth attempts.
IntCounter* total_cookie_auth_success_ = nullptr;
IntCounter* total_cookie_auth_failure_ = nullptr;
};
}