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/connection.cc b/libwebserv/connection.cc
new file mode 100644
index 0000000..4e1ae8f
--- /dev/null
+++ b/libwebserv/connection.cc
@@ -0,0 +1,158 @@
+// 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
new file mode 100644
index 0000000..73bb816
--- /dev/null
+++ b/libwebserv/connection.h
@@ -0,0 +1,89 @@
+// 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/export.h b/libwebserv/export.h
new file mode 100644
index 0000000..f2c5394
--- /dev/null
+++ b/libwebserv/export.h
@@ -0,0 +1,13 @@
+// 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_EXPORT_H_
+#define WEBSERVER_LIBWEBSERV_EXPORT_H_
+
+// See detailed explanation of the purpose of LIBWEBSERV_EXPORT in
+// chromeos/chromeos_export.h for similar attribute - CHROMEOS_EXPORT.
+#define LIBWEBSERV_EXPORT __attribute__((__visibility__("default")))
+#define LIBWEBSERV_PRIVATE __attribute__((__visibility__("hidden")))
+
+#endif // WEBSERVER_LIBWEBSERV_EXPORT_H_
diff --git a/libwebserv/libwebserv.pc.in b/libwebserv/libwebserv.pc.in
new file mode 100644
index 0000000..537d6bc
--- /dev/null
+++ b/libwebserv/libwebserv.pc.in
@@ -0,0 +1,7 @@
+bslot=@BSLOT@
+
+Name: libwebserv
+Description: Web server interface library
+Version: ${bslot}
+Requires.private: @PRIVATE_PC@
+Libs: -lwebserv-${bslot}
diff --git a/libwebserv/libwebserv_testrunner.cc b/libwebserv/libwebserv_testrunner.cc
new file mode 100644
index 0000000..575f952
--- /dev/null
+++ b/libwebserv/libwebserv_testrunner.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 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 <base/at_exit.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+ base::AtExitManager exit_manager;
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/libwebserv/preinstall.sh b/libwebserv/preinstall.sh
new file mode 100755
index 0000000..51ee83b
--- /dev/null
+++ b/libwebserv/preinstall.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# 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.
+
+set -e
+
+OUT=$1
+v=$2
+
+deps=$(<"${OUT}"/gen/libwebserv-${v}-deps.txt)
+sed \
+ -e "s/@BSLOT@/${v}/g" \
+ -e "s/@PRIVATE_PC@/${deps}/g" \
+ "libwebserv/libwebserv.pc.in" > "${OUT}/lib/libwebserv-${v}.pc"
diff --git a/libwebserv/request.cc b/libwebserv/request.cc
new file mode 100644
index 0000000..37ea1c1
--- /dev/null
+++ b/libwebserv/request.cc
@@ -0,0 +1,191 @@
+// 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/request.h>
+
+#include <libwebserv/connection.h>
+
+namespace libwebserv {
+
+FileInfo::FileInfo(const std::string& file_name,
+ const std::string& content_type,
+ const std::string& transfer_encoding)
+ : file_name_(file_name),
+ content_type_(content_type),
+ transfer_encoding_(transfer_encoding) {
+}
+
+const std::vector<uint8_t>& FileInfo::GetData() const {
+ return data_;
+}
+
+Request::Request(const std::string& url, const std::string& method)
+ : 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();
+ data.insert(data.end(), post_data.begin(), post_data.end());
+ return data;
+}
+
+std::vector<PairOfStrings> Request::GetFormDataGet() const {
+ return std::vector<PairOfStrings>{get_data_.begin(), get_data_.end()};
+}
+
+std::vector<PairOfStrings> Request::GetFormDataPost() const {
+ return std::vector<PairOfStrings>{post_data_.begin(), post_data_.end()};
+}
+
+std::vector<std::pair<std::string, const FileInfo*>> Request::GetFiles() const {
+ std::vector<std::pair<std::string, const FileInfo*>> data;
+ data.reserve(file_info_.size());
+ for (const auto& pair : file_info_) {
+ data.emplace_back(pair.first, pair.second.get());
+ }
+ return data;
+}
+
+std::vector<std::string> Request::GetFormField(const std::string& name) const {
+ std::vector<std::string> data;
+ auto pair = get_data_.equal_range(name);
+ while (pair.first != pair.second) {
+ data.push_back(pair.first->second);
+ ++pair.first;
+ }
+ pair = post_data_.equal_range(name);
+ while (pair.first != pair.second) {
+ data.push_back(pair.first->second);
+ ++pair.first;
+ }
+ return data;
+}
+
+std::vector<std::string> Request::GetFormFieldPost(
+ const std::string& name) const {
+ std::vector<std::string> data;
+ auto pair = post_data_.equal_range(name);
+ while (pair.first != pair.second) {
+ data.push_back(pair.first->second);
+ ++pair.first;
+ }
+ return data;
+}
+
+std::vector<std::string> Request::GetFormFieldGet(
+ const std::string& name) const {
+ std::vector<std::string> data;
+ auto pair = get_data_.equal_range(name);
+ while (pair.first != pair.second) {
+ data.push_back(pair.first->second);
+ ++pair.first;
+ }
+ return data;
+}
+
+std::vector<const FileInfo*> Request::GetFileInfo(
+ const std::string& name) const {
+ std::vector<const FileInfo*> data;
+ auto pair = file_info_.equal_range(name);
+ while (pair.first != pair.second) {
+ data.push_back(pair.first->second.get());
+ ++pair.first;
+ }
+ return data;
+}
+
+std::vector<PairOfStrings> Request::GetHeaders() const {
+ return std::vector<PairOfStrings>{headers_.begin(), headers_.end()};
+}
+
+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;
+ }
+ 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;
+}
+
+} // namespace libwebserv
diff --git a/libwebserv/request.h b/libwebserv/request.h
new file mode 100644
index 0000000..6899b3d
--- /dev/null
+++ b/libwebserv/request.h
@@ -0,0 +1,146 @@
+// 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_REQUEST_H_
+#define WEBSERVER_LIBWEBSERV_REQUEST_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/scoped_ptr.h>
+#include <libwebserv/export.h>
+
+struct MHD_Connection;
+
+namespace libwebserv {
+
+class Connection;
+using PairOfStrings = std::pair<std::string, std::string>;
+
+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_; }
+
+ private:
+ 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);
+};
+
+// A class that represents the HTTP request data.
+class LIBWEBSERV_EXPORT Request final {
+ 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
+ // "multipart/form-data"). If there is no request body, or the data has been
+ // pre-parsed by the server, the returned stream will be empty.
+ const std::vector<uint8_t>& GetData() const;
+
+ // Returns the request path (e.g. "/path/document").
+ const std::string& GetPath() const { return url_; }
+
+ // Returns the request method (e.g. "GET", "POST", etc).
+ const std::string& GetMethod() const { return method_; }
+
+ // Returns a list of key-value pairs that include values provided on the URL
+ // (e.g. "http://server.com/?foo=bar") and the non-file form fields in the
+ // POST data.
+ std::vector<PairOfStrings> GetFormData() const;
+
+ // Returns a list of key-value pairs for query parameters provided on the URL
+ // (e.g. "http://server.com/?foo=bar").
+ std::vector<PairOfStrings> GetFormDataGet() const;
+
+ // Returns a list of key-value pairs for the non-file form fields in the
+ // POST data.
+ std::vector<PairOfStrings> GetFormDataPost() const;
+
+ // Returns a list of file information records for all the file uploads in
+ // the POST request.
+ std::vector<std::pair<std::string, const FileInfo*>> GetFiles() const;
+
+ // Gets the values of form field with given |name|. This includes both
+ // values provided on the URL and as part of form data in POST request.
+ std::vector<std::string> GetFormField(const std::string& name) const;
+
+ // Gets the values of form field with given |name| for form data in POST
+ // request.
+ std::vector<std::string> GetFormFieldPost(const std::string& name) const;
+
+ // Gets the values of URL query parameters with given |name|.
+ std::vector<std::string> GetFormFieldGet(const std::string& name) const;
+
+ // Gets the file upload parameters for a file form field of given |name|.
+ std::vector<const FileInfo*> GetFileInfo(const std::string& name) const;
+
+ // 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|.
+ std::vector<std::string> GetHeader(const std::string& name) const;
+
+ private:
+ LIBWEBSERV_PRIVATE Request(const std::string& url, const std::string& method);
+
+ // 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);
+
+ std::string url_;
+ std::string method_;
+ std::vector<uint8_t> raw_data_;
+ bool last_posted_data_was_file_{false};
+
+ std::multimap<std::string, std::string> post_data_;
+ std::multimap<std::string, std::string> get_data_;
+ 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);
+};
+
+} // namespace libwebserv
+
+#endif // WEBSERVER_LIBWEBSERV_REQUEST_H_
diff --git a/libwebserv/request_handler_callback.cc b/libwebserv/request_handler_callback.cc
new file mode 100644
index 0000000..f5be1fb
--- /dev/null
+++ b/libwebserv/request_handler_callback.cc
@@ -0,0 +1,18 @@
+// 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/request_handler_callback.h>
+
+namespace libwebserv {
+
+RequestHandlerCallback::RequestHandlerCallback(
+ const base::Callback<HandlerSignature>& callback) : callback_(callback) {
+}
+
+void RequestHandlerCallback::HandleRequest(scoped_ptr<Request> request,
+ scoped_ptr<Response> response) {
+ callback_.Run(request.Pass(), response.Pass());
+}
+
+} // namespace libwebserv
diff --git a/libwebserv/request_handler_callback.h b/libwebserv/request_handler_callback.h
new file mode 100644
index 0000000..1be1817
--- /dev/null
+++ b/libwebserv/request_handler_callback.h
@@ -0,0 +1,34 @@
+// 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_REQUEST_HANDLER_CALLBACK_H_
+#define WEBSERVER_LIBWEBSERV_REQUEST_HANDLER_CALLBACK_H_
+
+#include <base/callback.h>
+#include <base/macros.h>
+#include <libwebserv/export.h>
+#include <libwebserv/request_handler_interface.h>
+
+namespace libwebserv {
+
+// A simple request handler that wraps a callback function.
+// Essentially, it redirects the RequestHandlerInterface::HandleRequest calls
+// to the provided callback.
+class LIBWEBSERV_EXPORT RequestHandlerCallback
+ : public RequestHandlerInterface {
+ public:
+ explicit RequestHandlerCallback(
+ const base::Callback<HandlerSignature>& callback);
+
+ void HandleRequest(scoped_ptr<Request> request,
+ scoped_ptr<Response> response) override;
+
+ private:
+ base::Callback<HandlerSignature> callback_;
+ DISALLOW_COPY_AND_ASSIGN(RequestHandlerCallback);
+};
+
+} // namespace libwebserv
+
+#endif // WEBSERVER_LIBWEBSERV_REQUEST_HANDLER_CALLBACK_H_
diff --git a/libwebserv/request_handler_interface.h b/libwebserv/request_handler_interface.h
new file mode 100644
index 0000000..0b12126
--- /dev/null
+++ b/libwebserv/request_handler_interface.h
@@ -0,0 +1,28 @@
+// 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_REQUEST_HANDLER_INTERFACE_H_
+#define WEBSERVER_LIBWEBSERV_REQUEST_HANDLER_INTERFACE_H_
+
+#include <base/memory/scoped_ptr.h>
+#include <libwebserv/request.h>
+#include <libwebserv/response.h>
+
+namespace libwebserv {
+
+// The base interface for HTTP request handlers. When registering a handler,
+// the RequestHandlerInterface is provided, and when a request comes in,
+// RequestHandlerInterface::HandleRequest() is called to process the data and
+// send response.
+class RequestHandlerInterface {
+ public:
+ using HandlerSignature = void(scoped_ptr<Request>, scoped_ptr<Response>);
+
+ virtual void HandleRequest(scoped_ptr<Request> request,
+ scoped_ptr<Response> response) = 0;
+};
+
+} // namespace libwebserv
+
+#endif // WEBSERVER_LIBWEBSERV_REQUEST_HANDLER_INTERFACE_H_
diff --git a/libwebserv/response.cc b/libwebserv/response.cc
new file mode 100644
index 0000000..c54747f
--- /dev/null
+++ b/libwebserv/response.cc
@@ -0,0 +1,110 @@
+// 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/response.h>
+
+#include <algorithm>
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <base/values.h>
+#include <chromeos/http/http_request.h>
+#include <chromeos/mime_utils.h>
+#include <chromeos/strings/string_utils.h>
+#include <libwebserv/connection.h>
+#include <microhttpd.h>
+
+namespace libwebserv {
+
+Response::Response(const scoped_refptr<Connection>& connection)
+ : connection_(connection) {
+}
+
+Response::~Response() {
+ if (!reply_sent_) {
+ ReplyWithError(chromeos::http::status_code::InternalServerError,
+ "Internal server error");
+ }
+}
+
+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);
+}
+
+void Response::AddHeaders(
+ const std::vector<std::pair<std::string, std::string>>& headers) {
+ headers_.insert(headers.begin(), headers.end());
+}
+
+void Response::Reply(int status_code,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type) {
+ status_code_ = status_code;
+ const uint8_t* byte_ptr = static_cast<const uint8_t*>(data);
+ data_.assign(byte_ptr, byte_ptr + data_size);
+ AddHeader(chromeos::http::response_header::kContentType, mime_type);
+ SendResponse();
+}
+
+void Response::ReplyWithText(int status_code,
+ const std::string& text,
+ const std::string& mime_type) {
+ Reply(status_code, text.data(), text.size(), mime_type);
+}
+
+void Response::ReplyWithJson(int status_code, const base::Value* json) {
+ std::string text;
+ base::JSONWriter::WriteWithOptions(json,
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ &text);
+ std::string mime_type = chromeos::mime::AppendParameter(
+ chromeos::mime::application::kJson,
+ chromeos::mime::parameters::kCharset,
+ "utf-8");
+ ReplyWithText(status_code, text, mime_type);
+}
+
+void Response::ReplyWithJson(int status_code,
+ const std::map<std::string, std::string>& json) {
+ base::DictionaryValue json_value;
+ for (const auto& pair : json) {
+ json_value.SetString(pair.first, pair.second);
+ }
+ ReplyWithJson(status_code, &json_value);
+}
+
+void Response::Redirect(int status_code, const std::string& redirect_url) {
+ AddHeader(chromeos::http::response_header::kLocation, redirect_url);
+ ReplyWithError(status_code, "");
+}
+
+void Response::ReplyWithError(int status_code, const std::string& error_text) {
+ status_code_ = status_code;
+ data_.assign(error_text.begin(), error_text.end());
+ SendResponse();
+}
+
+void Response::ReplyWithErrorNotFound() {
+ ReplyWithError(chromeos::http::status_code::NotFound, "Not Found");
+}
+
+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;
+}
+
+} // namespace libwebserv
diff --git a/libwebserv/response.h b/libwebserv/response.h
new file mode 100644
index 0000000..05465e3
--- /dev/null
+++ b/libwebserv/response.h
@@ -0,0 +1,97 @@
+// 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_RESPONSE_H_
+#define WEBSERVER_LIBWEBSERV_RESPONSE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#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;
+
+// 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> {
+ 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);
+
+ // Adds number of HTTP response headers to the response.
+ void AddHeaders(
+ const std::vector<std::pair<std::string, std::string>>& headers);
+
+ // Generic reply method for sending arbitrary binary data response.
+ void Reply(int status_code,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type);
+
+ // Reply with text body.
+ void ReplyWithText(int status_code,
+ const std::string& text,
+ const std::string& mime_type);
+
+ // Reply with JSON object. The content type will be "application/json".
+ void ReplyWithJson(int status_code, const base::Value* json);
+
+ // Special form for JSON response for simple objects that have a flat
+ // list of key-value pairs of string type.
+ void ReplyWithJson(int status_code,
+ const std::map<std::string, std::string>& json);
+
+ // Issue a redirect response, so the client browser loads a page at
+ // the URL specified in |redirect_url|. If this is not an external URL,
+ // it must be an absolute path starting at the root "/...".
+ void Redirect(int status_code, const std::string& redirect_url);
+
+ // Send a plain text response (with no Content-Type header).
+ // Usually used with error responses. |error_text| must be plain text.
+ void ReplyWithError(int status_code, const std::string& error_text);
+
+ // Send "404 Not Found" response.
+ void ReplyWithErrorNotFound();
+
+ private:
+ LIBWEBSERV_PRIVATE explicit Response(
+ const scoped_refptr<Connection>& connection);
+
+ LIBWEBSERV_PRIVATE void SendResponse();
+
+ scoped_refptr<Connection> connection_;
+ 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);
+};
+
+} // namespace libwebserv
+
+#endif // WEBSERVER_LIBWEBSERV_RESPONSE_H_
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
diff --git a/libwebserv/server.h b/libwebserv/server.h
new file mode 100644
index 0000000..6221115
--- /dev/null
+++ b/libwebserv/server.h
@@ -0,0 +1,121 @@
+// 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_SERVER_H_
+#define WEBSERVER_LIBWEBSERV_SERVER_H_
+
+#include <map>
+#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 <libwebserv/export.h>
+#include <libwebserv/request_handler_interface.h>
+
+struct MHD_Daemon;
+
+namespace base {
+class TaskRunner;
+} // namespace base
+
+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.
+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);
+
+ // 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);
+
+ // Stops the server.
+ bool Stop();
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // 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;
+
+ private:
+ MHD_Daemon* server_ = nullptr;
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ struct LIBWEBSERV_PRIVATE HandlerMapEntry {
+ std::string url;
+ std::string method;
+ std::unique_ptr<RequestHandlerInterface> handler;
+ };
+
+ std::map<int, HandlerMapEntry> request_handlers_;
+ int last_handler_id_{0};
+
+ friend class ServerHelper;
+ friend class Connection;
+ DISALLOW_COPY_AND_ASSIGN(Server);
+};
+
+} // namespace libwebserv
+
+#endif // WEBSERVER_LIBWEBSERV_SERVER_H_