webserver: Move web server functionality into webservd
Moved libmicrohttpd from libwebserv to webservd daemon. Added the
D-Bus interface between the web server daemon and client library.
Updated privetd to use the new interface.
BUG=brillo:10
TEST=`FEATURES=test emerge-link libwebserv privetd`
CQ-DEPEND=CL:245780,CL:245118,CL:*195757
Change-Id: I26bfab64c6a0fd9460a47fd3fa9205c89abb943a
Reviewed-on: https://chromium-review.googlesource.com/245980
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/libwebserv/connection.cc b/libwebserv/connection.cc
deleted file mode 100644
index 4e1ae8f..0000000
--- a/libwebserv/connection.cc
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2014 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <libwebserv/connection.h>
-
-#include <algorithm>
-#include <vector>
-
-#include <base/bind.h>
-#include <base/location.h>
-#include <base/logging.h>
-#include <base/task_runner.h>
-#include <chromeos/http/http_request.h>
-#include <libwebserv/request.h>
-#include <libwebserv/request_handler_interface.h>
-#include <libwebserv/response.h>
-#include <libwebserv/server.h>
-#include <microhttpd.h>
-
-namespace libwebserv {
-
-// Helper class to provide static callback methods to microhttpd library,
-// with the ability to access private methods of Connection class.
-class ConnectionHelper {
- public:
- static int PostDataIterator(void* cls,
- MHD_ValueKind kind,
- const char* key,
- const char* filename,
- const char* content_type,
- const char* transfer_encoding,
- const char* data,
- uint64_t off,
- size_t size) {
- Connection* server_connection = reinterpret_cast<Connection*>(cls);
- if (!server_connection->ProcessPostData(
- key, filename, content_type, transfer_encoding, data, off, size)) {
- return MHD_NO;
- }
- return MHD_YES;
- }
-};
-
-// Helper class to provide static callback methods to microhttpd library,
-// with the ability to access private methods of Request class.
-class RequestHelper {
- public:
- static int ValueCallback(void* cls,
- MHD_ValueKind kind,
- const char* key,
- const char* value) {
- auto self = reinterpret_cast<Request*>(cls);
- std::string data;
- if (value)
- data = value;
- if (kind == MHD_HEADER_KIND) {
- self->headers_.emplace(Request::GetCanonicalHeaderName(key), data);
- } else if (kind == MHD_COOKIE_KIND) {
- // TODO(avakulenko): add support for cookies...
- } else if (kind == MHD_POSTDATA_KIND) {
- self->post_data_.emplace(key, data);
- } else if (kind == MHD_GET_ARGUMENT_KIND) {
- self->get_data_.emplace(key, data);
- }
- return MHD_YES;
- }
-};
-
-Connection::Connection(const scoped_refptr<base::TaskRunner>& task_runner,
- MHD_Connection* connection,
- RequestHandlerInterface* handler)
- : task_runner_(task_runner),
- raw_connection_(connection),
- handler_(handler) {
-}
-
-Connection::~Connection() {
- if (post_processor_)
- MHD_destroy_post_processor(post_processor_);
-}
-
-scoped_refptr<Connection> Connection::Create(Server* server,
- const std::string& url,
- const std::string& method,
- MHD_Connection* connection,
- RequestHandlerInterface* handler) {
- scoped_refptr<Connection> result(
- new Connection(server->task_runner_, connection, handler));
- VLOG(1) << "Incoming HTTP connection (" << result.get() << ")."
- << " Method='" << method << "', URL='" << url << "'";
- result->post_processor_ = MHD_create_post_processor(
- connection, 1024, &ConnectionHelper::PostDataIterator, result.get());
- result->request_ = Request::Create(url, method);
- result->response_ = Response::Create(result);
- return result;
-}
-
-bool Connection::BeginRequestData() {
- MHD_get_connection_values(raw_connection_, MHD_HEADER_KIND,
- &RequestHelper::ValueCallback, request_.get());
- MHD_get_connection_values(raw_connection_, MHD_COOKIE_KIND,
- &RequestHelper::ValueCallback, request_.get());
- MHD_get_connection_values(raw_connection_, MHD_POSTDATA_KIND,
- &RequestHelper::ValueCallback, request_.get());
- MHD_get_connection_values(raw_connection_, MHD_GET_ARGUMENT_KIND,
- &RequestHelper::ValueCallback, request_.get());
- return true;
-}
-
-bool Connection::AddRequestData(const void* data, size_t size) {
- if (!post_processor_)
- return request_->AddRawRequestData(data, size);
- return MHD_post_process(post_processor_,
- static_cast<const char*>(data), size) == MHD_YES;
-}
-
-void Connection::EndRequestData() {
- if (state_ == State::kIdle) {
- state_ = State::kRequestSent;
- // libmicrohttpd calls handlers on its own thread.
- // Redirect this to the main IO thread of the server.
- task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RequestHandlerInterface::HandleRequest,
- base::Unretained(handler_), base::Passed(&request_),
- base::Passed(&response_)));
- } else if (state_ == State::kResponseReceived) {
- VLOG(1) << "Sending HTTP response for connection (" << this
- << "): " << response_status_code_
- << ", data size = " << response_data_.size();
- MHD_Response* resp = MHD_create_response_from_buffer(
- response_data_.size(), response_data_.data(), MHD_RESPMEM_PERSISTENT);
- for (const auto& pair : response_headers_) {
- MHD_add_response_header(resp, pair.first.c_str(), pair.second.c_str());
- }
- CHECK_EQ(MHD_YES,
- MHD_queue_response(raw_connection_, response_status_code_, resp))
- << "Failed to queue response";
- MHD_destroy_response(resp); // |resp| is ref-counted.
- state_ = State::kDone;
- }
-}
-
-bool Connection::ProcessPostData(const char* key,
- const char* filename,
- const char* content_type,
- const char* transfer_encoding,
- const char* data,
- uint64_t off,
- size_t size) {
- if (off == 0)
- return request_->AddPostFieldData(key, filename, content_type,
- transfer_encoding, data, size);
- return request_->AppendPostFieldData(key, data, size);
-}
-
-} // namespace libwebserv
diff --git a/libwebserv/connection.h b/libwebserv/connection.h
deleted file mode 100644
index 73bb816..0000000
--- a/libwebserv/connection.h
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2014 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef WEBSERVER_LIBWEBSERV_CONNECTION_H_
-#define WEBSERVER_LIBWEBSERV_CONNECTION_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include <base/macros.h>
-#include <base/memory/ref_counted.h>
-#include <base/memory/scoped_ptr.h>
-#include <chromeos/errors/error.h>
-#include <libwebserv/export.h>
-
-struct MHD_Connection;
-struct MHD_PostProcessor;
-
-namespace base {
-class TaskRunner;
-} // namespace base
-
-namespace libwebserv {
-
-class Request;
-class RequestHandlerInterface;
-class Response;
-class Server;
-
-// A wrapper class around low-level HTTP connection.
-class LIBWEBSERV_EXPORT Connection final : public base::RefCounted<Connection> {
- public:
- ~Connection();
-
- // Factory creator method. Creates an instance of the connection and
- // initializes some complex data members. This is safer and easier to
- // report possible failures than reply on just the constructor.
- static scoped_refptr<Connection> Create(Server* server,
- const std::string& url,
- const std::string& method,
- MHD_Connection* connection,
- RequestHandlerInterface* handler);
-
- private:
- LIBWEBSERV_PRIVATE Connection(
- const scoped_refptr<base::TaskRunner>& task_runner,
- MHD_Connection* connection,
- RequestHandlerInterface* handler);
-
- // Helper callback methods used by Server's ConnectionHandler to transfer
- // request headers and data to the Connection's Request object.
- LIBWEBSERV_PRIVATE bool BeginRequestData();
- LIBWEBSERV_PRIVATE bool AddRequestData(const void* data, size_t size);
- LIBWEBSERV_PRIVATE void EndRequestData();
-
- // Callback for libmicrohttpd's PostProcessor.
- LIBWEBSERV_PRIVATE bool ProcessPostData(const char* key,
- const char* filename,
- const char* content_type,
- const char* transfer_encoding,
- const char* data,
- uint64_t off,
- size_t size);
-
- scoped_refptr<base::TaskRunner> task_runner_;
- MHD_Connection* raw_connection_{nullptr};
- RequestHandlerInterface* handler_{nullptr};
- MHD_PostProcessor* post_processor_{nullptr};
- scoped_ptr<Request> request_;
- scoped_ptr<Response> response_;
-
- enum class State { kIdle, kRequestSent, kResponseReceived, kDone };
- State state_{State::kIdle};
- int response_status_code_{0};
- std::vector<uint8_t> response_data_;
- std::multimap<std::string, std::string> response_headers_;
-
- friend class ConnectionHelper;
- friend class Request;
- friend class Response;
- friend class ServerHelper;
- DISALLOW_COPY_AND_ASSIGN(Connection);
-};
-
-} // namespace libwebserv
-
-#endif // WEBSERVER_LIBWEBSERV_CONNECTION_H_
diff --git a/libwebserv/dbus_bindings/org.chromium.WebServer.RequestHandler.xml b/libwebserv/dbus_bindings/org.chromium.WebServer.RequestHandler.xml
new file mode 100644
index 0000000..5eab71d
--- /dev/null
+++ b/libwebserv/dbus_bindings/org.chromium.WebServer.RequestHandler.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/WebServer/RequestHandler"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.WebServer.RequestHandler">
+ <!-- Methods -->
+ <method name="ProcessRequest">
+ <tp:docstring>
+ Sends a new HTTP request to the handler.
+ Parameters:
+ - request_info - request metadata. Due to limitation of base::Callback
+ on the number of parameters, we have to collapse a couple
+ of distinct parameters into a larger struct, containing:
+ - (s) protocol_handler_id - ID of the protocol handler.
+ - (s) request_handler_id - ID of the registered request
+ handler.
+ - (s) request_id - unique ID of this request within the
+ protocol handler.
+ - (s) url - The request URL (e.g. "/path/object").
+ - (s) method - Request method (e.g. "GET", "POST", ...).
+ - headers - Request headers (key-value pairs)
+ - params - an array of request parameters which could be either
+ URL params (specified after "?" in the request URL), or
+ form fields in a POST request. Elements have the following
+ structure:
+ - (b) true = form field, false = URL param
+ - (s) field_name
+ - (s) field_value
+ - files - Information about uploaded files.
+ The data is an array of FileInfo structures containing the
+ following fields:
+ - (i) file_id
+ - (s) field_name
+ - (s) file_name
+ - (s) content_type
+ - (s) transfer_encoding
+ The actual contents of the file is obtained by calling
+ GetFileData() on the request object
+ - body - Raw unparsed request data. Could be empty for POST requests
+ that have form data/uploaded files already parsed into
+ form_fields/files parameters.
+ </tp:docstring>
+ <arg name="request_info" type="(sssss)" direction="in"/>
+ <arg name="headers" type="a(ss)" direction="in"/>
+ <arg name="params" type="a(bss)" direction="in"/>
+ <arg name="files" type="a(issss)" direction="in"/>
+ <arg name="body" type="ay" direction="in"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
+ </interface>
+</node>
diff --git a/libwebserv/protocol_handler.cc b/libwebserv/protocol_handler.cc
new file mode 100644
index 0000000..9a10acf
--- /dev/null
+++ b/libwebserv/protocol_handler.cc
@@ -0,0 +1,207 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webserver/libwebserv/protocol_handler.h"
+
+#include <tuple>
+
+#include <base/logging.h>
+#include <chromeos/map_utils.h>
+
+#include "libwebserv/org.chromium.WebServer.RequestHandler.h"
+#include "webservd/dbus-proxies.h"
+#include "webserver/libwebserv/request.h"
+#include "webserver/libwebserv/request_handler_callback.h"
+#include "webserver/libwebserv/response.h"
+#include "webserver/libwebserv/server.h"
+
+namespace libwebserv {
+
+namespace {
+
+// A dummy callback for async D-Bus errors.
+void IgnoreError(chromeos::Error* error) {}
+
+} // anonymous namespace
+
+const char ProtocolHandler::kHttp[] = "http";
+const char ProtocolHandler::kHttps[] = "https";
+
+ProtocolHandler::ProtocolHandler(const std::string& id, Server* server)
+ : id_{id}, server_{server} {}
+
+ProtocolHandler::~ProtocolHandler() {
+ // Remove any existing handlers, so the web server knows that we don't
+ // need them anymore.
+
+ // We need to get a copy of the map keys since removing the handlers will
+ // modify the map in the middle of the loop and that's not a good thing.
+ auto handler_ids = chromeos::GetMapKeys(request_handlers_);
+ for (int handler_id : handler_ids) {
+ RemoveHandler(handler_id);
+ }
+}
+
+std::string ProtocolHandler::GetID() const {
+ return id_;
+}
+
+uint16_t ProtocolHandler::GetPort() const {
+ CHECK(IsConnected());
+ return proxy_->port();
+}
+
+std::string ProtocolHandler::GetProtocol() const {
+ CHECK(IsConnected());
+ return proxy_->protocol();
+}
+
+chromeos::Blob ProtocolHandler::GetCertificateFingerprint() const {
+ CHECK(IsConnected());
+ return proxy_->certificate_fingerprint();
+}
+
+int ProtocolHandler::AddHandler(
+ const std::string& url,
+ const std::string& method,
+ std::unique_ptr<RequestHandlerInterface> handler) {
+ request_handlers_.emplace(++last_handler_id_,
+ HandlerMapEntry{url, method, std::string{},
+ std::move(handler)});
+ if (proxy_) {
+ proxy_->AddRequestHandlerAsync(
+ url,
+ method,
+ server_->service_name_,
+ base::Bind(&ProtocolHandler::AddHandlerSuccess,
+ weak_ptr_factory_.GetWeakPtr(),
+ last_handler_id_),
+ base::Bind(&ProtocolHandler::AddHandlerError,
+ weak_ptr_factory_.GetWeakPtr(),
+ last_handler_id_));
+ }
+ return last_handler_id_;
+}
+
+int ProtocolHandler::AddHandlerCallback(
+ const std::string& url,
+ const std::string& method,
+ const base::Callback<RequestHandlerInterface::HandlerSignature>&
+ handler_callback) {
+ std::unique_ptr<RequestHandlerInterface> handler{
+ new RequestHandlerCallback{handler_callback}};
+ return AddHandler(url, method, std::move(handler));
+}
+
+bool ProtocolHandler::RemoveHandler(int handler_id) {
+ auto p = request_handlers_.find(handler_id);
+ if (p == request_handlers_.end())
+ return false;
+
+ if (proxy_) {
+ proxy_->RemoveRequestHandlerAsync(
+ p->second.remote_handler_id,
+ base::Bind(&base::DoNothing),
+ base::Bind(&IgnoreError));
+ }
+
+ request_handlers_.erase(p);
+ return true;
+}
+
+void ProtocolHandler::Connect(
+ org::chromium::WebServer::ProtocolHandlerProxy* proxy) {
+ proxy_ = proxy;
+ for (const auto& pair : request_handlers_) {
+ proxy_->AddRequestHandlerAsync(
+ pair.second.url,
+ pair.second.method,
+ server_->service_name_,
+ base::Bind(&ProtocolHandler::AddHandlerSuccess,
+ weak_ptr_factory_.GetWeakPtr(),
+ pair.first),
+ base::Bind(&ProtocolHandler::AddHandlerError,
+ weak_ptr_factory_.GetWeakPtr(),
+ pair.first));
+ }
+}
+
+void ProtocolHandler::Disconnect() {
+ proxy_ = nullptr;
+ remote_handler_id_map_.clear();
+}
+
+void ProtocolHandler::AddHandlerSuccess(int handler_id,
+ const std::string& remote_handler_id) {
+ auto p = request_handlers_.find(handler_id);
+ CHECK(p != request_handlers_.end());
+ p->second.remote_handler_id = remote_handler_id;
+
+ remote_handler_id_map_.emplace(remote_handler_id, handler_id);
+}
+
+void ProtocolHandler::AddHandlerError(int handler_id, chromeos::Error* error) {
+ // Nothing to do at the moment.
+}
+
+bool ProtocolHandler::ProcessRequest(const std::string& remote_handler_id,
+ const std::string& request_id,
+ std::unique_ptr<Request> request,
+ chromeos::ErrorPtr* error) {
+ 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,
+ chromeos::errors::dbus::kDomain,
+ DBUS_ERROR_FAILED,
+ "Unknown request handler '%s'",
+ remote_handler_id.c_str());
+ return false;
+ }
+ auto handler_iter = request_handlers_.find(id_iter->second);
+ if (handler_iter == request_handlers_.end()) {
+ chromeos::Error::AddToPrintf(error, FROM_HERE,
+ chromeos::errors::dbus::kDomain,
+ DBUS_ERROR_FAILED,
+ "Handler # %d is no longer available",
+ id_iter->second);
+ return false;
+ }
+ handler_iter->second.handler->HandleRequest(
+ scoped_ptr<Request>(request.release()),
+ scoped_ptr<Response>(new Response{this, request_id}));
+ return true;
+}
+
+void ProtocolHandler::CompleteRequest(
+ const std::string& request_id,
+ 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";
+ 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*) {}));
+}
+
+void ProtocolHandler::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) {
+ CHECK(proxy_);
+ proxy_->GetRequestFileDataAsync(
+ request_id, file_id, success_callback, error_callback);
+}
+
+
+} // namespace libwebserv
diff --git a/libwebserv/protocol_handler.h b/libwebserv/protocol_handler.h
new file mode 100644
index 0000000..9ba7d11
--- /dev/null
+++ b/libwebserv/protocol_handler.h
@@ -0,0 +1,187 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBSERVER_LIBWEBSERV_PROTOCOL_HANDLER_H_
+#define WEBSERVER_LIBWEBSERV_PROTOCOL_HANDLER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/secure_blob.h>
+
+#include <libwebserv/export.h>
+#include <libwebserv/request_handler_interface.h>
+
+namespace org {
+namespace chromium {
+namespace WebServer {
+
+class ProtocolHandlerProxy;
+
+} // namespace WebServer
+} // namespace chromium
+} // namespace org
+
+namespace libwebserv {
+
+class Server;
+class Request;
+
+// Wrapper around a protocol handler (e.g. HTTP or HTTPs).
+// ProtocolHandler allows consumers to add request handlers on a given protocol.
+// When the ProtocolHandler is connected, allows users to read port and protocol
+// information.
+class LIBWEBSERV_EXPORT ProtocolHandler final {
+ public:
+ explicit ProtocolHandler(const std::string& id, 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; }
+
+ // 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;
+
+ // 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 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;
+
+ // 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.
+ chromeos::Blob GetCertificateFingerprint() const;
+
+ // Adds a request handler for given |url|. If the |url| ends with a '/', this
+ // makes the handler respond to any URL beneath this path.
+ // Note that it is not possible to add a specific handler just for the root
+ // path "/". Doing so means "respond to any URL".
+ // |method| is optional request method verb, such as "GET" or "POST".
+ // If |method| is empty, the handler responds to any request verb.
+ // If there are more than one handler for a given request, the most specific
+ // match is chosen. For example, if there are the following handlers provided:
+ // - A["/foo/", ""]
+ // - B["/foo/bar", "GET"]
+ // - C["/foo/bar", ""]
+ // Here is what handlers are called when making certain requests:
+ // - GET("/foo/bar") => B[]
+ // - POST("/foo/bar") => C[]
+ // - PUT("/foo/bar") => C[]
+ // - GET("/foo/baz") => A[]
+ // - GET("/foo") => 404 Not Found
+ // This functions returns a handler ID which can be used later to remove
+ // the handler.
+ //
+ // The handler registration information is stored inside ProtocolHandler and
+ // is used to register the handlers with the web server daemon when it becomes
+ // available. This also happens when the web server goes away and then comes
+ // back (e.g. restarted). So, there is no need to re-register the handlers
+ // once the web server process is restarted.
+ int AddHandler(const std::string& url,
+ const std::string& method,
+ std::unique_ptr<RequestHandlerInterface> handler);
+
+ // Similar to AddHandler() above but the handler is just a callback function.
+ int AddHandlerCallback(
+ const std::string& url,
+ const std::string& method,
+ const base::Callback<RequestHandlerInterface::HandlerSignature>&
+ handler_callback);
+
+ // Removes the handler with the specified |handler_id|.
+ // Returns false if the handler with the given ID is not found.
+ bool RemoveHandler(int handler_id);
+
+ static const char kHttp[];
+ static const char kHttps[];
+
+ private:
+ friend class FileInfo;
+ friend class Server;
+ friend class Response;
+
+ struct LIBWEBSERV_PRIVATE HandlerMapEntry {
+ std::string url;
+ std::string method;
+ std::string remote_handler_id;
+ std::unique_ptr<RequestHandlerInterface> handler;
+ };
+
+ // 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);
+ // Called by the Server class when the D-Bus proxy object gets disconnected
+ // from the web server daemon.
+ LIBWEBSERV_PRIVATE void Disconnect();
+
+ // Asynchronous callbacks to handle successful or failed request handler
+ // registration over D-Bus.
+ LIBWEBSERV_PRIVATE void AddHandlerSuccess(
+ int handler_id, const std::string& remote_handler_id);
+ LIBWEBSERV_PRIVATE void AddHandlerError(int handler_id,
+ chromeos::Error* error);
+
+ // Called by Server when an incoming request is dispatched.
+ LIBWEBSERV_PRIVATE bool ProcessRequest(const std::string& remote_handler_id,
+ const std::string& request_id,
+ std::unique_ptr<Request> request,
+ chromeos::ErrorPtr* error);
+
+ // Called by Response object to finish the request and send response data.
+ LIBWEBSERV_PRIVATE void CompleteRequest(
+ const std::string& request_id,
+ int status_code,
+ const std::multimap<std::string, std::string>& headers,
+ const std::vector<uint8_t>& data);
+
+ // Makes a call to the (remote) web server request handler over D-Bus to
+ // 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);
+
+ // Protocol Handler unique ID.
+ std::string id_;
+ // 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_;
+ // 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};
+
+ base::WeakPtrFactory<ProtocolHandler> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(ProtocolHandler);
+};
+
+} // namespace libwebserv
+
+#endif // WEBSERVER_LIBWEBSERV_PROTOCOL_HANDLER_H_
diff --git a/libwebserv/request.cc b/libwebserv/request.cc
index 37ea1c1..b51aa94 100644
--- a/libwebserv/request.cc
+++ b/libwebserv/request.cc
@@ -4,86 +4,49 @@
#include <libwebserv/request.h>
-#include <libwebserv/connection.h>
+#include <base/callback.h>
+#include <chromeos/http/http_utils.h>
+
+#include <libwebserv/protocol_handler.h>
namespace libwebserv {
-FileInfo::FileInfo(const std::string& file_name,
+FileInfo::FileInfo(ProtocolHandler* handler,
+ int file_id,
+ const std::string& request_id,
+ const std::string& file_name,
const std::string& content_type,
const std::string& transfer_encoding)
- : file_name_(file_name),
+ : handler_{handler},
+ file_id_{file_id},
+ request_id_{request_id},
+ file_name_(file_name),
content_type_(content_type),
transfer_encoding_(transfer_encoding) {
}
-const std::vector<uint8_t>& FileInfo::GetData() const {
- return data_;
+void FileInfo::GetData(
+ const base::Callback<void(const std::vector<uint8_t>&)>& success_callback,
+ const base::Callback<void(chromeos::Error*)>& error_callback) {
+ handler_->GetFileData(request_id_,
+ file_id_,
+ success_callback,
+ error_callback);
}
-Request::Request(const std::string& url, const std::string& method)
- : url_{url}, method_{method} {
+Request::Request(ProtocolHandler* handler,
+ const std::string& url,
+ const std::string& method)
+ : handler_{handler}, url_{url}, method_{method} {
}
Request::~Request() {
}
-scoped_ptr<Request> Request::Create(const std::string& url,
- const std::string& method) {
- // Can't use make_shared here since Request::Request is private.
- return scoped_ptr<Request>(new Request(url, method));
-}
-
const std::vector<uint8_t>& Request::GetData() const {
return raw_data_;
}
-bool Request::AddRawRequestData(const void* data, size_t size) {
- const uint8_t* byte_data_ = static_cast<const uint8_t*>(data);
- raw_data_.insert(raw_data_.end(), byte_data_, byte_data_ + size);
- return true;
-}
-
-bool Request::AddPostFieldData(const char* key,
- const char* filename,
- const char* content_type,
- const char* transfer_encoding,
- const char* data,
- size_t size) {
- if (filename) {
- std::unique_ptr<FileInfo> file_info{
- new FileInfo{filename, content_type ? content_type : "",
- transfer_encoding ? transfer_encoding : ""}};
- file_info->data_.assign(data, data + size);
- file_info_.emplace(key, std::move(file_info));
- last_posted_data_was_file_ = true;
- return true;
- }
- std::string value{data, size};
- post_data_.emplace(key, value);
- last_posted_data_was_file_ = false;
- return true;
-}
-
-bool Request::AppendPostFieldData(const char* key,
- const char* data,
- size_t size) {
- if (last_posted_data_was_file_) {
- auto file_pair = file_info_.equal_range(key);
- if (file_pair.first == file_info_.end())
- return false;
- FileInfo* file_info = file_pair.second->second.get();
- file_info->data_.insert(file_info->data_.end(), data, data + size);
- return true;
- }
-
- auto pair = post_data_.equal_range(key);
- if (pair.first == post_data_.end())
- return false;
- --pair.second; // Get the last form field with this name/key.
- pair.second->second.append(data, size);
- return true;
-}
-
std::vector<PairOfStrings> Request::GetFormData() const {
auto data = GetFormDataGet();
auto post_data = GetFormDataPost();
@@ -162,30 +125,19 @@
std::vector<std::string> Request::GetHeader(const std::string& name) const {
std::vector<std::string> data;
- auto pair = headers_.equal_range(GetCanonicalHeaderName(name));
- while (pair.first != pair.second) {
- data.push_back(pair.first->second);
- ++pair.first;
+ auto range =
+ headers_.equal_range(chromeos::http::GetCanonicalHeaderName(name));
+ while (range.first != range.second) {
+ data.push_back(range.first->second);
+ ++range.first;
}
return data;
}
-std::string Request::GetCanonicalHeaderName(const std::string& name) {
- std::string canonical_name = name;
- bool word_begin = true;
- for (char& c : canonical_name) {
- if (c == '-') {
- word_begin = true;
- } else {
- if (word_begin) {
- c = toupper(c);
- } else {
- c = tolower(c);
- }
- word_begin = false;
- }
- }
- return canonical_name;
+std::string Request::GetFirstHeader(const std::string& name) const {
+ auto p = headers_.find(chromeos::http::GetCanonicalHeaderName(name));
+ return (p != headers_.end()) ? p->second : std::string{};
}
+
} // namespace libwebserv
diff --git a/libwebserv/request.h b/libwebserv/request.h
index 6899b3d..f75622b 100644
--- a/libwebserv/request.h
+++ b/libwebserv/request.h
@@ -11,36 +11,50 @@
#include <utility>
#include <vector>
+#include <base/callback_forward.h>
#include <base/macros.h>
#include <base/memory/ref_counted.h>
#include <base/memory/scoped_ptr.h>
+#include <chromeos/errors/error.h>
#include <libwebserv/export.h>
struct MHD_Connection;
namespace libwebserv {
-class Connection;
+class ProtocolHandler;
+
using PairOfStrings = std::pair<std::string, std::string>;
+// This class represents the file information about a file uploaded via
+// POST request using multipart/form-data request.
class LIBWEBSERV_EXPORT FileInfo final {
public:
- FileInfo(const std::string& file_name,
- const std::string& content_type,
- const std::string& transfer_encoding);
-
- const std::vector<uint8_t>& GetData() const;
const std::string& GetFileName() const { return file_name_; }
const std::string& GetContentType() const { return content_type_; }
const std::string& GetTransferEncoding() const { return transfer_encoding_; }
+ void GetData(
+ const base::Callback<void(const std::vector<uint8_t>&)>& success_callback,
+ const base::Callback<void(chromeos::Error*)>& error_callback);
private:
+ friend class Server;
+
+ LIBWEBSERV_PRIVATE FileInfo(ProtocolHandler* handler,
+ int file_id,
+ const std::string& request_id,
+ const std::string& file_name,
+ const std::string& content_type,
+ const std::string& transfer_encoding);
+
+ ProtocolHandler* handler_{nullptr};
+ int file_id_{0};
+ std::string request_id_;
std::string file_name_;
std::string content_type_;
std::string transfer_encoding_;
std::vector<uint8_t> data_;
- friend class Request;
DISALLOW_COPY_AND_ASSIGN(FileInfo);
};
@@ -49,10 +63,6 @@
public:
~Request();
- // Factory constructor method.
- static scoped_ptr<Request> Create(const std::string& url,
- const std::string& method);
-
// Gets the request body data stream. Note that the stream is available
// only for requests that provided data and if this data is not already
// pre-parsed by the server (e.g. "application/x-www-form-urlencoded" and
@@ -100,32 +110,22 @@
// Returns a list of key-value pairs for all the request headers.
std::vector<PairOfStrings> GetHeaders() const;
- // Returns the value(s) of a request head of given |name|.
+ // Returns the value(s) of a request header of given |name|.
std::vector<std::string> GetHeader(const std::string& name) const;
+ // Returns the value of a request header of given |name|. If there are more
+ // than one header with this name, the value of the first header is returned.
+ // An empty string is returned if the header does not exist in the request.
+ std::string GetFirstHeader(const std::string& name) const;
+
private:
- LIBWEBSERV_PRIVATE Request(const std::string& url, const std::string& method);
+ friend class Server;
- // Helper methods for processing request data coming from the raw HTTP
- // connection.
- // These methods parse the request headers and data so they can be accessed
- // by request handlers later.
- LIBWEBSERV_PRIVATE bool AddRawRequestData(const void* data, size_t size);
- LIBWEBSERV_PRIVATE bool AddPostFieldData(const char* key,
- const char* filename,
- const char* content_type,
- const char* transfer_encoding,
- const char* data,
- size_t size);
- LIBWEBSERV_PRIVATE bool AppendPostFieldData(const char* key,
- const char* data,
- size_t size);
- // Converts a request header name to canonical form (lowercase with uppercase
- // first letter and each letter after a hyphen ('-')).
- // "content-TYPE" will be converted to "Content-Type".
- LIBWEBSERV_PRIVATE static std::string GetCanonicalHeaderName(
- const std::string& name);
+ LIBWEBSERV_PRIVATE Request(ProtocolHandler* handler,
+ const std::string& url,
+ const std::string& method);
+ ProtocolHandler* handler_{nullptr};
std::string url_;
std::string method_;
std::vector<uint8_t> raw_data_;
@@ -136,8 +136,6 @@
std::multimap<std::string, std::unique_ptr<FileInfo>> file_info_;
std::multimap<std::string, std::string> headers_;
- friend class Connection;
- friend class RequestHelper;
DISALLOW_COPY_AND_ASSIGN(Request);
};
diff --git a/libwebserv/response.cc b/libwebserv/response.cc
index c54747f..fc46e87 100644
--- a/libwebserv/response.cc
+++ b/libwebserv/response.cc
@@ -12,13 +12,12 @@
#include <chromeos/http/http_request.h>
#include <chromeos/mime_utils.h>
#include <chromeos/strings/string_utils.h>
-#include <libwebserv/connection.h>
-#include <microhttpd.h>
+#include <libwebserv/protocol_handler.h>
namespace libwebserv {
-Response::Response(const scoped_refptr<Connection>& connection)
- : connection_(connection) {
+Response::Response(ProtocolHandler* handler, const std::string& request_id)
+ : handler_{handler}, request_id_{request_id} {
}
Response::~Response() {
@@ -28,11 +27,6 @@
}
}
-scoped_ptr<Response> Response::Create(
- const scoped_refptr<Connection>& connection) {
- return scoped_ptr<Response>(new Response(connection));
-}
-
void Response::AddHeader(const std::string& header_name,
const std::string& value) {
headers_.emplace(header_name, value);
@@ -99,12 +93,7 @@
void Response::SendResponse() {
CHECK(!reply_sent_) << "Response already sent";
reply_sent_ = true;
- CHECK(connection_->state_ == Connection::State::kRequestSent)
- << "Unexpected connection state";
- connection_->response_status_code_ = status_code_;
- connection_->response_data_ = std::move(data_);
- connection_->response_headers_ = std::move(headers_);
- connection_->state_ = Connection::State::kResponseReceived;
+ handler_->CompleteRequest(request_id_, status_code_, headers_, data_);
}
} // namespace libwebserv
diff --git a/libwebserv/response.h b/libwebserv/response.h
index 05465e3..616264d 100644
--- a/libwebserv/response.h
+++ b/libwebserv/response.h
@@ -12,31 +12,22 @@
#include <vector>
#include <base/macros.h>
-#include <base/memory/ref_counted.h>
-#include <base/memory/scoped_ptr.h>
#include <libwebserv/export.h>
namespace base {
class Value;
} // namespace base
-struct MHD_Connection;
-
namespace libwebserv {
-class Connection;
+class ProtocolHandler;
// Response class is a proxy for HTTP response used by the request handler
// to provide response HTTP headers and data.
-class LIBWEBSERV_EXPORT Response
- : public std::enable_shared_from_this<Response> {
+class LIBWEBSERV_EXPORT Response final {
public:
~Response();
- // Factory constructor method.
- static scoped_ptr<Response> Create(
- const scoped_refptr<Connection>& connection);
-
// Adds a single HTTP response header to the response.
void AddHeader(const std::string& header_name, const std::string& value);
@@ -76,19 +67,20 @@
void ReplyWithErrorNotFound();
private:
- LIBWEBSERV_PRIVATE explicit Response(
- const scoped_refptr<Connection>& connection);
+ friend class ProtocolHandler;
+
+ LIBWEBSERV_PRIVATE Response(ProtocolHandler* handler,
+ const std::string& request_id);
LIBWEBSERV_PRIVATE void SendResponse();
- scoped_refptr<Connection> connection_;
+ ProtocolHandler* handler_{nullptr};
+ std::string request_id_;
int status_code_{0};
std::vector<uint8_t> data_;
std::multimap<std::string, std::string> headers_;
bool reply_sent_{false};
- friend class Connection;
- friend class ResponseHelper;
DISALLOW_COPY_AND_ASSIGN(Response);
};
diff --git a/libwebserv/server.cc b/libwebserv/server.cc
index 196f178..2bf7c7d 100644
--- a/libwebserv/server.cc
+++ b/libwebserv/server.cc
@@ -4,230 +4,201 @@
#include <libwebserv/server.h>
-#include <limits>
+#include <tuple>
#include <vector>
-#include <base/logging.h>
-#include <base/message_loop/message_loop_proxy.h>
-#include <libwebserv/connection.h>
+#include <libwebserv/protocol_handler.h>
#include <libwebserv/request.h>
-#include <libwebserv/request_handler_callback.h>
-#include <libwebserv/request_handler_interface.h>
-#include <libwebserv/response.h>
-#include <microhttpd.h>
+
+#include "libwebserv/org.chromium.WebServer.RequestHandler.h"
+#include "webservd/dbus-proxies.h"
namespace libwebserv {
-namespace {
-// Simple static request handler that just returns "404 Not Found" error.
-class PageNotFoundHandler : public RequestHandlerInterface {
+class Server::RequestHandler final
+ : public org::chromium::WebServer::RequestHandlerInterface {
public:
- void HandleRequest(scoped_ptr<Request> request,
- scoped_ptr<Response> response) override {
- response->ReplyWithErrorNotFound();
- }
+ explicit RequestHandler(Server* server) : server_{server} {}
+ bool ProcessRequest(
+ chromeos::ErrorPtr* error,
+ const std::tuple<std::string, std::string, std::string, std::string,
+ std::string>& in_request_info,
+ const std::vector<std::tuple<std::string, std::string>>& in_headers,
+ const std::vector<std::tuple<bool, std::string, std::string>>& in_params,
+ const std::vector<std::tuple<int32_t, std::string, std::string,
+ std::string, std::string>>& in_files,
+ const std::vector<uint8_t>& in_body) override;
+
+ private:
+ Server* server_{nullptr};
+ DISALLOW_COPY_AND_ASSIGN(RequestHandler);
};
-} // anonymous namespace
-
-// Helper class to provide static callback methods to microhttpd library,
-// with the ability to access private methods of Server class.
-class ServerHelper {
- public:
- static int ConnectionHandler(void *cls,
- MHD_Connection* connection,
- const char* url,
- const char* method,
- const char* version,
- const char* upload_data,
- size_t* upload_data_size,
- void** con_cls) {
- Server* server = reinterpret_cast<Server*>(cls);
- if (nullptr == *con_cls) {
- RequestHandlerInterface* handler = server->FindHandler(url, method);
- if (!handler)
- return MHD_NO;
-
- auto server_connection =
- Connection::Create(server, url, method, connection, handler);
- if (!server_connection || !server_connection->BeginRequestData())
- return MHD_NO;
-
- *con_cls = server_connection.get();
- server_connection->AddRef();
- } else {
- Connection* connection = reinterpret_cast<Connection*>(*con_cls);
- if (*upload_data_size) {
- if (!connection->AddRequestData(upload_data, *upload_data_size))
- return MHD_NO;
- *upload_data_size = 0;
- } else {
- connection->EndRequestData();
- }
- }
- return MHD_YES;
+bool Server::RequestHandler::ProcessRequest(
+ chromeos::ErrorPtr* error,
+ const std::tuple<std::string, std::string, std::string, std::string,
+ std::string>& in_request_info,
+ const std::vector<std::tuple<std::string, std::string>>& in_headers,
+ const std::vector<std::tuple<bool, std::string, std::string>>& in_params,
+ const std::vector<std::tuple<int32_t, std::string, std::string,
+ std::string, std::string>>& in_files,
+ const std::vector<uint8_t>& in_body) {
+ std::string protocol_handler_id = std::get<0>(in_request_info);
+ std::string request_handler_id = std::get<1>(in_request_info);
+ std::string request_id = std::get<2>(in_request_info);
+ 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);
+ if (!protocol_handler) {
+ chromeos::Error::AddToPrintf(error, FROM_HERE,
+ chromeos::errors::dbus::kDomain,
+ DBUS_ERROR_FAILED,
+ "Unknown protocol handler '%s'",
+ protocol_handler_id.c_str());
+ return false;
+ }
+ std::unique_ptr<Request> request{new Request{protocol_handler, url, method}};
+ // Convert request data into format required by the Request object.
+ for (const auto& tuple : in_params) {
+ if (std::get<0>(tuple))
+ request->post_data_.emplace(std::get<1>(tuple), std::get<2>(tuple));
+ else
+ request->get_data_.emplace(std::get<1>(tuple), std::get<2>(tuple));
}
- static void RequestCompleted(void* cls,
- MHD_Connection* connection,
- void** con_cls,
- MHD_RequestTerminationCode toe) {
- reinterpret_cast<Connection*>(*con_cls)->Release();
- *con_cls = nullptr;
- }
-};
+ for (const auto& tuple : in_headers)
+ request->headers_.emplace(std::get<0>(tuple), std::get<1>(tuple));
-Server::Server() {}
+ for (const auto& tuple : in_files) {
+ request->file_info_.emplace(
+ std::get<1>(tuple), // field_name
+ std::unique_ptr<FileInfo>{new FileInfo{
+ protocol_handler,
+ std::get<0>(tuple), // file_id
+ request_id,
+ std::get<2>(tuple), // file_name
+ std::get<3>(tuple), // content_type
+ std::get<4>(tuple)}}); // transfer_encoding
+ }
+
+ request->raw_data_ = in_body;
+
+ return protocol_handler->ProcessRequest(request_handler_id,
+ request_id,
+ std::move(request),
+ error);
+}
+
+Server::Server()
+ : request_handler_{new RequestHandler{this}},
+ dbus_adaptor_{new org::chromium::WebServer::RequestHandlerAdaptor{
+ request_handler_.get()}} {}
Server::~Server() {
- Stop();
}
-bool Server::Start(uint16_t port) {
- return StartWithTLS(port, chromeos::SecureBlob{}, chromeos::Blob{});
+void Server::Connect(
+ const scoped_refptr<dbus::Bus>& bus,
+ const std::string& service_name,
+ const chromeos::dbus_utils::AsyncEventSequencer::CompletionAction& cb,
+ const base::Closure& on_server_online,
+ const base::Closure& on_server_offline) {
+ service_name_ = service_name;
+ dbus_object_.reset(new chromeos::dbus_utils::DBusObject{
+ nullptr, bus, dbus_adaptor_->GetObjectPath()});
+ dbus_adaptor_->RegisterWithDBusObject(dbus_object_.get());
+ dbus_object_->RegisterAsync(cb);
+ on_server_online_ = on_server_online;
+ on_server_offline_ = on_server_offline;
+ AddProtocolHandler(std::unique_ptr<ProtocolHandler>{
+ new ProtocolHandler{ProtocolHandler::kHttp, this}});
+ AddProtocolHandler(std::unique_ptr<ProtocolHandler>{
+ new ProtocolHandler{ProtocolHandler::kHttps, this}});
+ object_manager_.reset(new org::chromium::WebServer::ObjectManagerProxy{bus});
+ object_manager_->SetServerAddedCallback(
+ base::Bind(&Server::Online, base::Unretained(this)));
+ object_manager_->SetServerRemovedCallback(
+ base::Bind(&Server::Offline, base::Unretained(this)));
+ object_manager_->SetProtocolHandlerAddedCallback(
+ base::Bind(&Server::ProtocolHandlerAdded, base::Unretained(this)));
+ object_manager_->SetProtocolHandlerRemovedCallback(
+ base::Bind(&Server::ProtocolHandlerRemoved, base::Unretained(this)));
}
-bool Server::StartWithTLS(uint16_t port,
- const chromeos::SecureBlob& private_key,
- const chromeos::Blob& certificate) {
- if (server_) {
- LOG(ERROR) << "Web server is already running.";
- return false;
+void Server::Disconnect() {
+ object_manager_.reset();
+ on_server_offline_.Reset();
+ on_server_online_.Reset();
+ dbus_object_.reset();
+ protocol_handlers_.clear();
+}
+
+void Server::Online(org::chromium::WebServer::ServerProxy* server) {
+ proxy_ = server;
+ if (!on_server_online_.is_null())
+ on_server_online_.Run();
+}
+
+void Server::Offline(const dbus::ObjectPath& object_path) {
+ if (!on_server_offline_.is_null())
+ on_server_offline_.Run();
+ proxy_ = nullptr;
+}
+
+void Server::ProtocolHandlerAdded(
+ org::chromium::WebServer::ProtocolHandlerProxy* handler) {
+ protocol_handler_id_map_.emplace(handler->GetObjectPath(), handler->id());
+ ProtocolHandler* registered_handler = GetProtocolHandler(handler->id());
+ if (registered_handler) {
+ registered_handler->Connect(handler);
+ if (!on_protocol_handler_connected_.is_null())
+ on_protocol_handler_connected_.Run(registered_handler);
+ }
+}
+
+void Server::ProtocolHandlerRemoved(const dbus::ObjectPath& object_path) {
+ auto p = protocol_handler_id_map_.find(object_path);
+ if (p == protocol_handler_id_map_.end())
+ return;
+
+ ProtocolHandler* registered_handler = GetProtocolHandler(p->second);
+ if (registered_handler) {
+ if (!on_protocol_handler_disconnected_.is_null())
+ on_protocol_handler_disconnected_.Run(registered_handler);
+ registered_handler->Disconnect();
}
- // Either both keys and certificate must be specified or both muse be omitted.
- CHECK_EQ(private_key.empty(), certificate.empty());
-
- const bool use_tls = !private_key.empty();
-
- task_runner_ = base::MessageLoopProxy::current();
-
- LOG(INFO) << "Starting " << (use_tls ? "HTTPS" : "HTTP")
- << " Server on port: " << port;
- auto callback_addr =
- reinterpret_cast<intptr_t>(&ServerHelper::RequestCompleted);
- uint32_t flags = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG;
-
- std::vector<MHD_OptionItem> options{
- {MHD_OPTION_CONNECTION_LIMIT, 10, nullptr},
- {MHD_OPTION_CONNECTION_TIMEOUT, 60, nullptr},
- {MHD_OPTION_NOTIFY_COMPLETED, callback_addr, nullptr},
- };
-
- // libmicrohttpd expects both the key and certificate to be a zero-terminated
- // strings. Make sure they are terminated properly.
- chromeos::SecureBlob private_key_copy = private_key;
- chromeos::Blob certificate_copy = certificate;
- if (use_tls) {
- flags |= MHD_USE_SSL;
- private_key_copy.push_back(0);
- certificate_copy.push_back(0);
- options.push_back(
- MHD_OptionItem{MHD_OPTION_HTTPS_MEM_KEY, 0, private_key_copy.data()});
- options.push_back(
- MHD_OptionItem{MHD_OPTION_HTTPS_MEM_CERT, 0, certificate_copy.data()});
- }
-
- options.push_back(MHD_OptionItem{MHD_OPTION_END, 0, nullptr});
-
- server_ = MHD_start_daemon(flags, port, nullptr, nullptr,
- &ServerHelper::ConnectionHandler, this,
- MHD_OPTION_ARRAY, options.data(), MHD_OPTION_END);
- if (!server_) {
- LOG(ERROR) << "Failed to start the web server on port " << port;
- return false;
- }
- LOG(INFO) << "Server started";
- return true;
+ protocol_handler_id_map_.erase(p);
}
-bool Server::Stop() {
- if (server_) {
- LOG(INFO) << "Shutting down the web server...";
- MHD_stop_daemon(server_);
- server_ = nullptr;
- LOG(INFO) << "Server shutdown complete";
- }
- return true;
+ProtocolHandler* Server::GetProtocolHandler(const std::string& id) const {
+ auto p = protocol_handlers_.find(id);
+ return (p != protocol_handlers_.end()) ? p->second.get() : nullptr;
}
-int Server::AddHandler(const base::StringPiece& url,
- const base::StringPiece& method,
- std::unique_ptr<RequestHandlerInterface> handler) {
- request_handlers_.emplace(++last_handler_id_,
- HandlerMapEntry{url.as_string(),
- method.as_string(),
- std::move(handler)});
- return last_handler_id_;
+ProtocolHandler* Server::GetDefaultHttpHandler() const {
+ return GetProtocolHandler(ProtocolHandler::kHttp);
}
-int Server::AddHandlerCallback(
- const base::StringPiece& url,
- const base::StringPiece& method,
- const base::Callback<RequestHandlerInterface::HandlerSignature>&
- handler_callback) {
- std::unique_ptr<RequestHandlerInterface> handler{
- new RequestHandlerCallback{handler_callback}};
- return AddHandler(url, method, std::move(handler));
+ProtocolHandler* Server::GetDefaultHttpsHandler() const {
+ return GetProtocolHandler(ProtocolHandler::kHttps);
}
-bool Server::RemoveHandler(int handler_id) {
- return (request_handlers_.erase(handler_id) > 0);
+void Server::OnProtocolHandlerConnected(
+ const base::Callback<void(ProtocolHandler*)>& callback) {
+ on_protocol_handler_connected_ = callback;
}
-int Server::GetHandlerId(const base::StringPiece& url,
- const base::StringPiece& method) const {
- for (const auto& pair : request_handlers_) {
- if (pair.second.url == url && pair.second.method == method)
- return pair.first;
- }
- return 0;
+void Server::OnProtocolHandlerDisconnected(
+ const base::Callback<void(ProtocolHandler*)>& callback) {
+ on_protocol_handler_disconnected_ = callback;
}
-RequestHandlerInterface* Server::FindHandler(
- const base::StringPiece& url,
- const base::StringPiece& method) const {
- static PageNotFoundHandler page_not_found_handler;
-
- size_t score = std::numeric_limits<size_t>::max();
- RequestHandlerInterface* handler = nullptr;
- for (const auto& pair : request_handlers_) {
- std::string handler_url = pair.second.url;
- bool url_match = (handler_url == url);
- bool method_match = (pair.second.method == method);
-
- // Try exact match first. If everything matches, we have our handler.
- if (url_match && method_match)
- return pair.second.handler.get();
-
- // Calculate the current handler's similarity score. The lower the score
- // the better the match is...
- size_t current_score = 0;
- if (!url_match && !handler_url.empty() && handler_url.back() == '/') {
- if (url.starts_with(handler_url)) {
- url_match = true;
- // Use the difference in URL length as URL match quality proxy.
- // The longer URL, the more specific (better) match is.
- // Multiply by 2 to allow for extra score point for matching the method.
- current_score = (url.size() - handler_url.size()) * 2;
- }
- }
-
- if (!method_match && pair.second.method.empty()) {
- // If the handler didn't specify the method it handles, this means
- // it doesn't care. However this isn't the exact match, so bump
- // the score up one point.
- method_match = true;
- ++current_score;
- }
-
- if (url_match && method_match && current_score < score) {
- score = current_score;
- handler = pair.second.handler.get();
- }
- }
-
- return handler ? handler : &page_not_found_handler;
+void Server::AddProtocolHandler(std::unique_ptr<ProtocolHandler> handler) {
+ // Make sure a handler with this ID isn't already registered.
+ CHECK(protocol_handlers_.find(handler->GetID()) == protocol_handlers_.end());
+ protocol_handlers_.emplace(handler->GetID(), std::move(handler));
}
} // namespace libwebserv
diff --git a/libwebserv/server.h b/libwebserv/server.h
index 6221115..ba133df 100644
--- a/libwebserv/server.h
+++ b/libwebserv/server.h
@@ -9,110 +9,135 @@
#include <memory>
#include <string>
-#include <base/callback_forward.h>
#include <base/macros.h>
-#include <base/memory/ref_counted.h>
-#include <base/strings/string_piece.h>
-#include <chromeos/secure_blob.h>
+#include <dbus/bus.h>
+#include <chromeos/dbus/async_event_sequencer.h>
+#include <chromeos/dbus/dbus_object.h>
#include <libwebserv/export.h>
#include <libwebserv/request_handler_interface.h>
-struct MHD_Daemon;
+namespace org {
+namespace chromium {
+namespace WebServer {
-namespace base {
-class TaskRunner;
-} // namespace base
+class ObjectManagerProxy;
+class ProtocolHandlerProxy;
+class RequestHandlerAdaptor;
+class ServerProxy;
+
+} // namespace WebServer
+} // namespace chromium
+} // namespace org
namespace libwebserv {
// Top-level wrapper class around HTTP server and provides an interface to
-// the web server. It allows the users to start the server and
-// register request handlers.
+// the web server.
class LIBWEBSERV_EXPORT Server final {
public:
Server();
~Server();
- // Starts the server and makes it listen to HTTP requests on the given port.
- //
- // Note that a valid message loop must exist before calling Start.
- bool Start(uint16_t port);
+ // Establish a connection to the system webserver.
+ // |service_name| is the well known D-Bus name of the client's process, used
+ // to expose a callback D-Bus object the web server calls back with incoming
+ // requests.
+ // |on_server_online| and |on_server_offline| will notify the caller when the
+ // server comes up and down.
+ // Note that we can Connect() even before the webserver attaches to D-Bus,
+ // and appropriate state will be built up when the webserver appears on D-Bus.
+ void Connect(
+ const scoped_refptr<dbus::Bus>& bus,
+ const std::string& service_name,
+ const chromeos::dbus_utils::AsyncEventSequencer::CompletionAction& cb,
+ const base::Closure& on_server_online,
+ const base::Closure& on_server_offline);
- // Starts the server and makes it listen to HTTPS requests on the given port.
- //
- // Note that a valid message loop must exist before calling StartWithTLS.
- bool StartWithTLS(uint16_t port,
- const chromeos::SecureBlob& private_key,
- const chromeos::Blob& certificate);
+ // Disconnects from the web server and removes the library interface from
+ // D-Bus.
+ void Disconnect();
- // Stops the server.
- bool Stop();
+ // A helper method that returns the default handler for "http".
+ ProtocolHandler* GetDefaultHttpHandler() const;
- // Adds a request handler for given |url|. If the |url| ends with a '/', this
- // makes the handler respond to any URL beneath this path.
- // Note that it is not possible to add a specific handler just for the root
- // path "/". Doing so means "respond to any URL".
- // |method| is optional request method verb, such as "GET" or "POST".
- // If |method| is empty, the handler responds to any request verb.
- // If there are more than one handler for a given request, the most specific
- // match is chosen. For example, if there are the following handlers provided:
- // - A["/foo/", ""]
- // - B["/foo/bar", "GET"]
- // - C["/foo/bar", ""]
- // Here is what handlers are called when making certain requests:
- // - GET("/foo/bar") => B[]
- // - POST("/foo/bar") => C[]
- // - PUT("/foo/bar") => C[]
- // - GET("/foo/baz") => A[]
- // - GET("/foo") => 404 Not Found
- // This functions returns a handler ID which can be used later to remove
- // the handler.
- int AddHandler(const base::StringPiece& url,
- const base::StringPiece& method,
- std::unique_ptr<RequestHandlerInterface> handler);
+ // A helper method that returns the default handler for "https".
+ ProtocolHandler* GetDefaultHttpsHandler() const;
- // Similar to AddHandler() above but the handler is just a callback function.
- int AddHandlerCallback(
- const base::StringPiece& url,
- const base::StringPiece& method,
- const base::Callback<RequestHandlerInterface::HandlerSignature>&
- handler_callback);
+ // Returns true if the web server daemon is connected to DBus and our
+ // connection to it has been established.
+ bool IsConnected() const { return proxy_ != nullptr; }
- // Removes the handler with the specified |handler_id|.
- // Returns false if the handler with the given ID is not found.
- bool RemoveHandler(int handler_id);
+ // Set a user-callback to be invoked when a protocol handler is connect to the
+ // server daemon. Multiple calls to this method will overwrite previously set
+ // callbacks.
+ void OnProtocolHandlerConnected(
+ const base::Callback<void(ProtocolHandler*)>& callback);
- // Finds the handler ID given the exact match criteria. Note that using
- // this function could cause unexpected side effects if there are more than
- // one handler registered for given URL/Method parameters.
- // It is better to remember the handler ID from AddHandler() method and use
- // that ID to remove the handler, instead of looking the handler up using
- // URL/Method.
- int GetHandlerId(const base::StringPiece& url,
- const base::StringPiece& method) const;
-
- // Finds a handler for given URL/Method. This method does the criteria
- // matching and not exact match performed by GetHandlerId. This is the method
- // used to look up the handler for incoming HTTP requests.
- RequestHandlerInterface* FindHandler(
- const base::StringPiece& url,
- const base::StringPiece& method) const;
+ // Set a user-callback to be invoked when a protocol handler is disconnected
+ // from the server daemon (e.g. on shutdown). Multiple calls to this method
+ // will overwrite previously set callbacks.
+ void OnProtocolHandlerDisconnected(
+ const base::Callback<void(ProtocolHandler*)>& callback);
private:
- MHD_Daemon* server_ = nullptr;
- scoped_refptr<base::TaskRunner> task_runner_;
+ friend class ProtocolHandler;
+ class RequestHandler;
- struct LIBWEBSERV_PRIVATE HandlerMapEntry {
- std::string url;
- std::string method;
- std::unique_ptr<RequestHandlerInterface> handler;
- };
+ // Returns an existing protocol handler by ID. See documentation in
+ // ProtocolHandler about IDs and how they work with webservd.
+ LIBWEBSERV_PRIVATE ProtocolHandler* GetProtocolHandler(
+ const std::string& id) const;
- std::map<int, HandlerMapEntry> request_handlers_;
- int last_handler_id_{0};
+ // Handler invoked when a connection is established to web server daemon.
+ LIBWEBSERV_PRIVATE void Online(org::chromium::WebServer::ServerProxy* server);
- friend class ServerHelper;
- friend class Connection;
+ // Handler invoked when the web server daemon connection is dropped.
+ LIBWEBSERV_PRIVATE void Offline(const dbus::ObjectPath& object_path);
+
+ // Handler invoked when a new protocol handler D-Bus proxy object becomes
+ // available.
+ LIBWEBSERV_PRIVATE void ProtocolHandlerAdded(
+ org::chromium::WebServer::ProtocolHandlerProxy* handler);
+
+ // Handler invoked when a protocol handler D-Bus proxy object disappears.
+ LIBWEBSERV_PRIVATE void ProtocolHandlerRemoved(
+ const dbus::ObjectPath& object_path);
+
+ LIBWEBSERV_PRIVATE void AddProtocolHandler(
+ std::unique_ptr<ProtocolHandler> handler);
+
+ // 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_;
+ // D-Bus object adaptor for RequestHandlerInterface.
+ std::unique_ptr<org::chromium::WebServer::RequestHandlerAdaptor>
+ dbus_adaptor_;
+ // D-Bus object to handler registration of RequestHandlerInterface.
+ std::unique_ptr<chromeos::dbus_utils::DBusObject> dbus_object_;
+
+ // 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_;
+ // 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_;
+
+ // User-specified callbacks for server and protocol handler life-time events.
+ base::Closure on_server_online_;
+ base::Closure on_server_offline_;
+ base::Callback<void(ProtocolHandler*)> on_protocol_handler_connected_;
+ base::Callback<void(ProtocolHandler*)> on_protocol_handler_disconnected_;
+
+ // D-Bus object manager proxy that receives notification of web server
+ // daemon's D-Bus object creation and destruction.
+ std::unique_ptr<org::chromium::WebServer::ObjectManagerProxy> object_manager_;
+
+ // D-Bus proxy for the web server main object.
+ org::chromium::WebServer::ServerProxy* proxy_{nullptr};
+
+ // D-Bus service name used by the daemon hosting this object.
+ std::string service_name_;
+
DISALLOW_COPY_AND_ASSIGN(Server);
};