webserver: Moved platform2/libwebserv to platform2/webserver
Cleaned up the code structure a bit inside the web server directory
to reflect the current layout of functionality:
- Renamed the top-level director from libwebserv to webserver
- Moved the client library code to sub-directory of libwebserv to
be on the same level as the web server daemon (webservd).
- Updated the source code to reflect the new include paths.
BUG=brillo:10
TEST=FEATURES=test emerge-storm webserver privetd ap-daemons
CQ-DEPEND=CL:245755,CL:*195317
Change-Id: Idb8d665b6e0c15b5ee0219ff72327045a7084363
Reviewed-on: https://chromium-review.googlesource.com/245736
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/libwebserv/server.cc b/libwebserv/server.cc
new file mode 100644
index 0000000..196f178
--- /dev/null
+++ b/libwebserv/server.cc
@@ -0,0 +1,233 @@
+// 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/server.h>
+
+#include <limits>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/message_loop/message_loop_proxy.h>
+#include <libwebserv/connection.h>
+#include <libwebserv/request.h>
+#include <libwebserv/request_handler_callback.h>
+#include <libwebserv/request_handler_interface.h>
+#include <libwebserv/response.h>
+#include <microhttpd.h>
+
+namespace libwebserv {
+
+namespace {
+// Simple static request handler that just returns "404 Not Found" error.
+class PageNotFoundHandler : public RequestHandlerInterface {
+ public:
+ void HandleRequest(scoped_ptr<Request> request,
+ scoped_ptr<Response> response) override {
+ response->ReplyWithErrorNotFound();
+ }
+};
+
+} // 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;
+ }
+
+ static void RequestCompleted(void* cls,
+ MHD_Connection* connection,
+ void** con_cls,
+ MHD_RequestTerminationCode toe) {
+ reinterpret_cast<Connection*>(*con_cls)->Release();
+ *con_cls = nullptr;
+ }
+};
+
+Server::Server() {}
+
+Server::~Server() {
+ Stop();
+}
+
+bool Server::Start(uint16_t port) {
+ return StartWithTLS(port, chromeos::SecureBlob{}, chromeos::Blob{});
+}
+
+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;
+ }
+
+ // 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;
+}
+
+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;
+}
+
+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_;
+}
+
+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));
+}
+
+bool Server::RemoveHandler(int handler_id) {
+ return (request_handlers_.erase(handler_id) > 0);
+}
+
+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;
+}
+
+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;
+}
+
+} // namespace libwebserv