webserver: Add support for multiple protocol handlers with the same name
Previously protocol handlers were designed with their ID to be unique.
If the device needed to server requests on multiple ports, the clients
were forced to register separate request handlers for each of the
unique protocol handler instance.
This change makes it possible to have multiple protocol handlers share
a common "name" (like "http" or "https") and all the request handlers
registered for a particular protocol handler with a given name will be
added to each protocol handler that has this name.
So, the implementation has changed a bit. Each protocol handler still
has a unique ID (GUID) that allows to identify each instance (for example
in order to send back the reply from the client to the exact instance
of the object that initiated the request), while leaving the rest of
client-side library (libwebserv) working with non-unique names when
registering request handlers.
This also changed the web server configuration file a bit, since it
used to use a JSON dictionary for each protocol handler config, now
its an array of objects, which allows to specify the same name for
more than one protocol handler.
BUG=brillo:412
TEST=`FEATURES=test emerge-link webserver privetd`
Change-Id: Ib67fb586dd249c834d2464bfa0ef0eaabb7d780f
Reviewed-on: https://chromium-review.googlesource.com/255262
Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Aaron Kemp <kemp@google.com>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/libwebserv/protocol_handler.cc b/libwebserv/protocol_handler.cc
index 9a10acf..fe4d567 100644
--- a/libwebserv/protocol_handler.cc
+++ b/libwebserv/protocol_handler.cc
@@ -28,8 +28,9 @@
const char ProtocolHandler::kHttp[] = "http";
const char ProtocolHandler::kHttps[] = "https";
-ProtocolHandler::ProtocolHandler(const std::string& id, Server* server)
- : id_{id}, server_{server} {}
+ProtocolHandler::ProtocolHandler(const std::string& name, Server* server)
+ : name_{name}, server_{server} {
+}
ProtocolHandler::~ProtocolHandler() {
// Remove any existing handlers, so the web server knows that we don't
@@ -43,23 +44,32 @@
}
}
-std::string ProtocolHandler::GetID() const {
- return id_;
+std::string ProtocolHandler::GetName() const {
+ return name_;
}
-uint16_t ProtocolHandler::GetPort() const {
- CHECK(IsConnected());
- return proxy_->port();
+std::set<uint16_t> ProtocolHandler::GetPorts() const {
+ std::set<uint16_t> ports;
+ for (const auto& pair : proxies_)
+ ports.insert(pair.second->port());
+ return ports;
}
-std::string ProtocolHandler::GetProtocol() const {
- CHECK(IsConnected());
- return proxy_->protocol();
+std::set<std::string> ProtocolHandler::GetProtocols() const {
+ std::set<std::string> protocols;
+ for (const auto& pair : proxies_)
+ protocols.insert(pair.second->protocol());
+ return protocols;
}
chromeos::Blob ProtocolHandler::GetCertificateFingerprint() const {
- CHECK(IsConnected());
- return proxy_->certificate_fingerprint();
+ chromeos::Blob fingerprint;
+ for (const auto& pair : proxies_) {
+ fingerprint = pair.second->certificate_fingerprint();
+ if (!fingerprint.empty())
+ break;
+ }
+ return fingerprint;
}
int ProtocolHandler::AddHandler(
@@ -69,8 +79,8 @@
request_handlers_.emplace(++last_handler_id_,
HandlerMapEntry{url, method, std::string{},
std::move(handler)});
- if (proxy_) {
- proxy_->AddRequestHandlerAsync(
+ for (const auto& pair : proxies_) {
+ pair.second->AddRequestHandlerAsync(
url,
method,
server_->service_name_,
@@ -99,8 +109,8 @@
if (p == request_handlers_.end())
return false;
- if (proxy_) {
- proxy_->RemoveRequestHandlerAsync(
+ for (const auto& pair : proxies_) {
+ pair.second->RemoveRequestHandlerAsync(
p->second.remote_handler_id,
base::Bind(&base::DoNothing),
base::Bind(&IgnoreError));
@@ -110,11 +120,10 @@
return true;
}
-void ProtocolHandler::Connect(
- org::chromium::WebServer::ProtocolHandlerProxy* proxy) {
- proxy_ = proxy;
+void ProtocolHandler::Connect(ProtocolHandlerProxy* proxy) {
+ proxies_.emplace(proxy->GetObjectPath(), proxy);
for (const auto& pair : request_handlers_) {
- proxy_->AddRequestHandlerAsync(
+ proxy->AddRequestHandlerAsync(
pair.second.url,
pair.second.method,
server_->service_name_,
@@ -127,9 +136,10 @@
}
}
-void ProtocolHandler::Disconnect() {
- proxy_ = nullptr;
- remote_handler_id_map_.clear();
+void ProtocolHandler::Disconnect(const dbus::ObjectPath& object_path) {
+ proxies_.erase(object_path);
+ if (proxies_.empty())
+ remote_handler_id_map_.clear();
}
void ProtocolHandler::AddHandlerSuccess(int handler_id,
@@ -145,10 +155,12 @@
// Nothing to do at the moment.
}
-bool ProtocolHandler::ProcessRequest(const std::string& remote_handler_id,
+bool ProtocolHandler::ProcessRequest(const std::string& protocol_handler_id,
+ const std::string& remote_handler_id,
const std::string& request_id,
std::unique_ptr<Request> request,
chromeos::ErrorPtr* error) {
+ request_id_map_.emplace(request_id, protocol_handler_id);
auto id_iter = remote_handler_id_map_.find(remote_handler_id);
if (id_iter == remote_handler_id_map_.end()) {
chromeos::Error::AddToPrintf(error, FROM_HERE,
@@ -178,19 +190,18 @@
int status_code,
const std::multimap<std::string, std::string>& headers,
const std::vector<uint8_t>& data) {
- if (!proxy_) {
- LOG(WARNING) << "Completing a request after the handler proxy is removed";
+ ProtocolHandlerProxy* proxy = GetRequestProtocolHandlerProxy(request_id);
+ if (!proxy)
return;
- }
std::vector<std::tuple<std::string, std::string>> header_list;
header_list.reserve(headers.size());
for (const auto& pair : headers)
header_list.emplace_back(pair.first, pair.second);
- proxy_->CompleteRequestAsync(request_id, status_code, header_list, data,
- base::Bind(&base::DoNothing),
- base::Bind([](chromeos::Error*) {}));
+ proxy->CompleteRequestAsync(request_id, status_code, header_list, data,
+ base::Bind(&base::DoNothing),
+ base::Bind([](chromeos::Error*) {}));
}
void ProtocolHandler::GetFileData(
@@ -198,10 +209,33 @@
int file_id,
const base::Callback<void(const std::vector<uint8_t>&)>& success_callback,
const base::Callback<void(chromeos::Error*)>& error_callback) {
- CHECK(proxy_);
- proxy_->GetRequestFileDataAsync(
+ ProtocolHandlerProxy* proxy = GetRequestProtocolHandlerProxy(request_id);
+ CHECK(proxy);
+
+ proxy->GetRequestFileDataAsync(
request_id, file_id, success_callback, error_callback);
}
+ProtocolHandler::ProtocolHandlerProxy*
+ProtocolHandler::GetRequestProtocolHandlerProxy(
+ const std::string& request_id) const {
+ auto iter = request_id_map_.find(request_id);
+ if (iter == request_id_map_.end()) {
+ LOG(ERROR) << "Can't find pending request with ID " << request_id;
+ return nullptr;
+ }
+ std::string handler_id = iter->second;
+ auto find_proxy_by_id = [handler_id](decltype(*proxies_.begin()) pair) {
+ return pair.second->id() == handler_id;
+ };
+ auto proxy_iter = std::find_if(proxies_.begin(), proxies_.end(),
+ find_proxy_by_id);
+ if (proxy_iter == proxies_.end()) {
+ LOG(WARNING) << "Completing a request after the handler proxy is removed";
+ return nullptr;
+ }
+ return proxy_iter->second;
+}
+
} // namespace libwebserv
diff --git a/libwebserv/protocol_handler.h b/libwebserv/protocol_handler.h
index 9ba7d11..bd8afb2 100644
--- a/libwebserv/protocol_handler.h
+++ b/libwebserv/protocol_handler.h
@@ -7,6 +7,7 @@
#include <map>
#include <memory>
+#include <set>
#include <string>
#include <vector>
@@ -15,6 +16,7 @@
#include <base/memory/weak_ptr.h>
#include <chromeos/errors/error.h>
#include <chromeos/secure_blob.h>
+#include <dbus/object_path.h>
#include <libwebserv/export.h>
#include <libwebserv/request_handler_interface.h>
@@ -40,33 +42,32 @@
// information.
class LIBWEBSERV_EXPORT ProtocolHandler final {
public:
- explicit ProtocolHandler(const std::string& id, Server* server);
+ explicit ProtocolHandler(const std::string& name, Server* server);
~ProtocolHandler();
// Returns true if the protocol handler object is connected to the web server
// daemon's proxy object and is capable of processing incoming requests.
- bool IsConnected() const { return proxy_ != nullptr; }
+ bool IsConnected() const { return !proxies_.empty(); }
- // Handler's unique ID. This is generally a GUID string, except for the
- // default HTTP and HTTPS handlers which have IDs of "http" and "https"
- // respectively.
- std::string GetID() const;
+ // Handler's name identifier (as provided in "name" setting of config file).
+ // Standard/default handler names are "http" and "https".
+ std::string GetName() const;
- // Returns the port the handler is bound to.
- // ATTENTION: The handler must be connected to the web server before this
- // method can be called.
- uint16_t GetPort() const;
+ // Returns the ports the handler is bound to. There could be multiple.
+ // If the handler is not connected to the server, this will return an empty
+ // set.
+ std::set<uint16_t> GetPorts() const;
// Returns the transport protocol that is served by this handler.
// Can be either "http" or "https".
- // ATTENTION: The handler must be connected to the web server before this
- // method can be called.
- std::string GetProtocol() const;
+ // If the handler is not connected to the server, this will return an empty
+ // set.
+ std::set<std::string> GetProtocols() const;
// Returns a SHA-256 fingerprint of HTTPS certificate used. Returns an empty
// byte buffer if this handler does not serve the HTTPS protocol.
- // ATTENTION: The handler must be connected to the web server before this
- // method can be called.
+ // If the handler is not connected to the server, this will return an empty
+ // array.
chromeos::Blob GetCertificateFingerprint() const;
// Adds a request handler for given |url|. If the |url| ends with a '/', this
@@ -117,6 +118,8 @@
friend class Server;
friend class Response;
+ using ProtocolHandlerProxy = org::chromium::WebServer::ProtocolHandlerProxy;
+
struct LIBWEBSERV_PRIVATE HandlerMapEntry {
std::string url;
std::string method;
@@ -127,11 +130,10 @@
// Called by the Server class when the D-Bus proxy object gets connected
// to the web server daemon.
- LIBWEBSERV_PRIVATE void Connect(
- org::chromium::WebServer::ProtocolHandlerProxy* proxy);
+ LIBWEBSERV_PRIVATE void Connect(ProtocolHandlerProxy* proxy);
// Called by the Server class when the D-Bus proxy object gets disconnected
// from the web server daemon.
- LIBWEBSERV_PRIVATE void Disconnect();
+ LIBWEBSERV_PRIVATE void Disconnect(const dbus::ObjectPath& object_path);
// Asynchronous callbacks to handle successful or failed request handler
// registration over D-Bus.
@@ -141,7 +143,8 @@
chromeos::Error* error);
// Called by Server when an incoming request is dispatched.
- LIBWEBSERV_PRIVATE bool ProcessRequest(const std::string& remote_handler_id,
+ LIBWEBSERV_PRIVATE bool ProcessRequest(const std::string& protocol_handler_id,
+ const std::string& remote_handler_id,
const std::string& request_id,
std::unique_ptr<Request> request,
chromeos::ErrorPtr* error);
@@ -157,26 +160,36 @@
// obtain the file content of uploaded file (identified by |file_id|) during
// request with |request_id|.
LIBWEBSERV_PRIVATE void GetFileData(
- const std::string& request_id,
- int file_id,
- const base::Callback<void(const std::vector<uint8_t>&)>& success_callback,
- const base::Callback<void(chromeos::Error*)>& error_callback);
+ const std::string& request_id,
+ int file_id,
+ const base::Callback<void(const std::vector<uint8_t>&)>& success_callback,
+ const base::Callback<void(chromeos::Error*)>& error_callback);
- // Protocol Handler unique ID.
- std::string id_;
+ // A helper method to obtain a corresponding protocol handler D-Bus proxy for
+ // outstanding request with ID |request_id|.
+ LIBWEBSERV_PRIVATE ProtocolHandlerProxy* GetRequestProtocolHandlerProxy(
+ const std::string& request_id) const;
+
+ // Protocol Handler name.
+ std::string name_;
// Back reference to the server object.
Server* server_{nullptr};
// Handler data map. The key is the client-facing request handler ID returned
// by AddHandler() when registering the handler.
std::map<int, HandlerMapEntry> request_handlers_;
+ // The counter to generate new handler IDs.
+ int last_handler_id_{0};
// Map of remote handler IDs (GUID strings) to client-facing request handler
// IDs (int) which are returned by AddHandler() and used as a key in
// |request_handlers_|.
std::map<std::string, int> remote_handler_id_map_;
- // The counter to generate new handler IDs.
- int last_handler_id_{0};
- // Remove D-Bus proxy for the server protocol handler object.
- org::chromium::WebServer::ProtocolHandlerProxy* proxy_{nullptr};
+ // Remote D-Bus proxies for the server protocol handler objects.
+ // There could be multiple protocol handlers with the same name (to make
+ // it possible to server the same requests on different ports, for example).
+ std::map<dbus::ObjectPath, ProtocolHandlerProxy*> proxies_;
+ // A map of request ID to protocol handler ID. Used to locate the appropriate
+ // protocol handler D-Bus proxy for given request.
+ std::map<std::string, std::string> request_id_map_;
base::WeakPtrFactory<ProtocolHandler> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ProtocolHandler);
diff --git a/libwebserv/server.cc b/libwebserv/server.cc
index 4b8403e..ed38988 100644
--- a/libwebserv/server.cc
+++ b/libwebserv/server.cc
@@ -49,7 +49,7 @@
std::string url = std::get<3>(in_request_info);
std::string method = std::get<4>(in_request_info);
ProtocolHandler* protocol_handler =
- server_->GetProtocolHandler(protocol_handler_id);
+ server_->GetProtocolHandlerByID(protocol_handler_id);
if (!protocol_handler) {
chromeos::Error::AddToPrintf(error, FROM_HERE,
chromeos::errors::dbus::kDomain,
@@ -84,7 +84,8 @@
request->raw_data_ = in_body;
- return protocol_handler->ProcessRequest(request_handler_id,
+ return protocol_handler->ProcessRequest(protocol_handler_id,
+ request_handler_id,
request_id,
std::move(request),
error);
@@ -127,7 +128,8 @@
on_server_offline_.Reset();
on_server_online_.Reset();
dbus_object_.reset();
- protocol_handlers_.clear();
+ protocol_handlers_ids_.clear();
+ protocol_handlers_names_.clear();
}
void Server::Online(org::chromium::WebServer::ServerProxy* server) {
@@ -147,11 +149,12 @@
void Server::ProtocolHandlerAdded(
org::chromium::WebServer::ProtocolHandlerProxy* handler) {
VLOG(1) << "Server-side protocol handler with ID '" << handler->id()
- << "' is on-line.";
+ << "' is on-line (" << handler->name() << ")";
protocol_handler_id_map_.emplace(handler->GetObjectPath(), handler->id());
- ProtocolHandler* registered_handler = GetProtocolHandler(handler->id());
+ ProtocolHandler* registered_handler = GetProtocolHandler(handler->name());
if (registered_handler) {
+ protocol_handlers_ids_.emplace(handler->id(), registered_handler);
registered_handler->Connect(handler);
if (!on_protocol_handler_connected_.is_null())
on_protocol_handler_connected_.Run(registered_handler);
@@ -166,24 +169,26 @@
VLOG(1) << "Server-side protocol handler with ID '" << p->second
<< "' is off-line.";
- ProtocolHandler* registered_handler = GetProtocolHandler(p->second);
+ ProtocolHandler* registered_handler = GetProtocolHandlerByID(p->second);
if (registered_handler) {
if (!on_protocol_handler_disconnected_.is_null())
on_protocol_handler_disconnected_.Run(registered_handler);
- registered_handler->Disconnect();
+ registered_handler->Disconnect(object_path);
+ protocol_handlers_ids_.erase(p->second);
}
protocol_handler_id_map_.erase(p);
}
-ProtocolHandler* Server::GetProtocolHandler(const std::string& id) {
- auto p = protocol_handlers_.find(id);
- if (p == protocol_handlers_.end()) {
+ProtocolHandler* Server::GetProtocolHandler(const std::string& name) {
+ auto p = protocol_handlers_names_.find(name);
+ if (p == protocol_handlers_names_.end()) {
VLOG(1) << "Creating a client-side instance of web server's protocol "
- << "handler with ID '" << id << "'";
- p = protocol_handlers_.emplace(
- id,
- std::unique_ptr<ProtocolHandler>{new ProtocolHandler{id, this}}).first;
+ << "handler with name '" << name << "'";
+ p = protocol_handlers_names_.emplace(
+ name,
+ std::unique_ptr<ProtocolHandler>{new ProtocolHandler{name, this}})
+ .first;
}
return p->second.get();
}
@@ -196,6 +201,15 @@
return GetProtocolHandler(ProtocolHandler::kHttps);
}
+ProtocolHandler* Server::GetProtocolHandlerByID(const std::string& id) const {
+ auto p = protocol_handlers_ids_.find(id);
+ if (p == protocol_handlers_ids_.end()) {
+ LOG(ERROR) << "Unable to locate protocol handler with ID '" << id << "'";
+ return nullptr;
+ }
+ return p->second;
+}
+
void Server::OnProtocolHandlerConnected(
const base::Callback<void(ProtocolHandler*)>& callback) {
on_protocol_handler_connected_ = callback;
diff --git a/libwebserv/server.h b/libwebserv/server.h
index 5e8f88a..bb45204 100644
--- a/libwebserv/server.h
+++ b/libwebserv/server.h
@@ -63,16 +63,14 @@
// A helper method that returns the default handler for "https".
ProtocolHandler* GetDefaultHttpsHandler();
- // Returns an existing protocol handler by ID. If the handler with the
- // requested |id| does not exist, a new one will be created. See
- // documentation in ProtocolHandler about IDs and how they work with
- // webservd.
+ // Returns an existing protocol handler by name. If the handler with the
+ // requested |name| does not exist, a new one will be created.
//
// The created handler is purely client side, and depends on the server
- // being configured to open a corresponding handler with the given ID.
+ // being configured to open a corresponding handler with the given name.
// Because clients and the server come up asynchronously, we allow clients
// to register anticipated handlers before server starts up.
- ProtocolHandler* GetProtocolHandler(const std::string& id);
+ ProtocolHandler* GetProtocolHandler(const std::string& name);
// Returns true if the web server daemon is connected to DBus and our
// connection to it has been established.
@@ -109,6 +107,10 @@
LIBWEBSERV_PRIVATE void ProtocolHandlerRemoved(
const dbus::ObjectPath& object_path);
+ // Looks up a protocol handler by ID. If not found, returns nullptr.
+ LIBWEBSERV_PRIVATE ProtocolHandler* GetProtocolHandlerByID(
+ const std::string& id) const;
+
// Private implementation of D-Bus RequestHandlerInterface called by the web
// server daemon whenever a new request is available to be processed.
std::unique_ptr<RequestHandler> request_handler_;
@@ -118,9 +120,11 @@
// D-Bus object to handler registration of RequestHandlerInterface.
std::unique_ptr<chromeos::dbus_utils::DBusObject> dbus_object_;
+ // A mapping of protocol handler name to the associated object.
+ std::map<std::string, std::unique_ptr<ProtocolHandler>>
+ protocol_handlers_names_;
// A mapping of protocol handler IDs to the associated object.
- // Handler IDs are either GUIDs or the two well-known handler IDs.
- std::map<std::string, std::unique_ptr<ProtocolHandler>> protocol_handlers_;
+ std::map<std::string, ProtocolHandler*> protocol_handlers_ids_;
// A map between D-Bus object path of protocol handler and remote protocol
// handler ID.
std::map<dbus::ObjectPath, std::string> protocol_handler_id_map_;