From 975b5709addd72cba7184f94f2805f1c42e07dbe Mon Sep 17 00:00:00 2001 From: Mauro Sardara Date: Wed, 22 Feb 2017 23:24:21 +0100 Subject: First commit: http-server Change-Id: Ia527fe3065016404b0ea752ddc9e15c96288ed86 Signed-off-by: Mauro Sardara --- AUTHORS | 6 + CMakeLists.txt | 53 +++++ README.md | 76 ++++++ cmake/Modules/FindLibicnet.cmake | 39 ++++ cmake/Modules/version.cmake | 15 ++ http-server/common.h | 40 ++++ http-server/configuration.cc | 56 +++++ http-server/configuration.h | 52 +++++ http-server/content.cc | 34 +++ http-server/content.h | 38 +++ http-server/http_server.cc | 487 +++++++++++++++++++++++++++++++++++++++ http-server/http_server.h | 125 ++++++++++ http-server/icn_request.cc | 63 +++++ http-server/icn_request.h | 56 +++++ http-server/icn_response.cc | 44 ++++ http-server/icn_response.h | 44 ++++ http-server/request.cc | 64 +++++ http-server/request.h | 83 +++++++ http-server/response.cc | 44 ++++ http-server/response.h | 50 ++++ http-server/socket_request.cc | 37 +++ http-server/socket_request.h | 44 ++++ http-server/socket_response.cc | 54 +++++ http-server/socket_response.h | 52 +++++ main.cc | 215 +++++++++++++++++ 25 files changed, 1871 insertions(+) create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/Modules/FindLibicnet.cmake create mode 100644 cmake/Modules/version.cmake create mode 100644 http-server/common.h create mode 100644 http-server/configuration.cc create mode 100644 http-server/configuration.h create mode 100644 http-server/content.cc create mode 100644 http-server/content.h create mode 100644 http-server/http_server.cc create mode 100644 http-server/http_server.h create mode 100644 http-server/icn_request.cc create mode 100644 http-server/icn_request.h create mode 100644 http-server/icn_response.cc create mode 100644 http-server/icn_response.h create mode 100644 http-server/request.cc create mode 100644 http-server/request.h create mode 100644 http-server/response.cc create mode 100644 http-server/response.h create mode 100644 http-server/socket_request.cc create mode 100644 http-server/socket_request.h create mode 100644 http-server/socket_response.cc create mode 100644 http-server/socket_response.h create mode 100644 main.cc diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..38bc0602 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +icnet authors are listed below + + Mauro Sardara + Ole Christian Eidheim (Origin Project) + +Copyright (c) 2016-2017 Cisco and/or its affiliates. \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..16c9891e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (c) 2017 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.2) +project(http-server) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3") +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") + +find_package(Libicnet REQUIRED) +include_directories(${LIBICNET_INCLUDE_DIRS}) + +find_package(Threads REQUIRED) + +find_package(Boost 1.53.0 COMPONENTS regex system thread filesystem date_time REQUIRED) +include_directories(SYSTEM ${Boost_INCLUDE_DIR} ${LIBICNET_INCLUDE_DIR}) + +set(SOURCE_FILES + main.cc + http-server/http_server.cc + http-server/http_server.h + http-server/response.cc + http-server/response.h + http-server/common.h + http-server/socket_response.cc + http-server/socket_response.h + http-server/icn_response.cc + http-server/icn_response.h + http-server/content.cc + http-server/content.h + http-server/request.cc + http-server/request.h + http-server/icn_request.cc + http-server/icn_request.h + http-server/socket_request.cc + http-server/socket_request.h + http-server/configuration.cc + http-server/configuration.h) + +add_executable(http-server ${SOURCE_FILES}) +target_link_libraries(http-server ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${LIBICNET_LIBRARY}) + +install(TARGETS http-server DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) diff --git a/README.md b/README.md new file mode 100644 index 00000000..a317c80c --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +HTTP Server over TCP/ICN +==================================================== +This is an implementation of a HTTP server able to serve client requests +using both TCP and ICN as transport protocol. + +This project is a fork from the http server implemented by Ole Christian Eidheim and +open sourced at https://github.com/eidheim/Simple-Web-Server. + +In the ICN flavour, so far, we support just the GET method. Later we'll be implementing the +remaining methods as well. + +Dependencies +------------ + +- libboost-regex-dev +- libboost-system-dev +- libboost-filesystem-dev +- libicnet + +Build the HTTP-Server +----------------- + +For building the library, from the root folder of the project: + +```bash + $ mkdir build && cd build + $ cmake .. + $ make +``` + +Install the HTTP-Server +------------------- + +For installing the application: + +```bash + $ cd build + $ sudo make install +``` + +Usage +----- + +For starting the http-server, from the build folder: + +```bash + $ cd build + $ ./http-server +``` + +The server now is: +- serving files from the folder **/var/www/html** +- Listening on the icn name /webserver +- Listening on the TCP port 8080 + +For retrieving a content through icn, the name must have the following format: + +`iget http://webserver/get/file.mp4` + +The server accept two option through the command line: + +```bash + $ ./http-server -h + http-server [-p PATH_TO_ROOT_FOOT_FOLDER] [-l WEBSERVER_PREFIX] +``` + +The default values are **/vaw/www/html** for the root folder and **ccnx:/webserver** for the icn name. + +Platforms +--------- + +Libicnet has been tested in: + + - Ubuntu 16.04 (x86_64) + - Debian Testing + - MacOSX 10.12 \ No newline at end of file diff --git a/cmake/Modules/FindLibicnet.cmake b/cmake/Modules/FindLibicnet.cmake new file mode 100644 index 00000000..3c9c4f5f --- /dev/null +++ b/cmake/Modules/FindLibicnet.cmake @@ -0,0 +1,39 @@ +######################################## +# +# Find the Libparc libraries and includes +# This module sets: +# LIBICNET_FOUND: True if Libconsumer-producer was found +# LIBICNETR_LIBRARY: The Libconsumer-producer library +# LIBICNET_LIBRARIES: The Libconsumer-producer library and dependencies +# LIBICNET_INCLUDE_DIR: The Libconsumer-producer include dir +# + +set(LIBICNET_SEARCH_PATH_LIST + ${LIBICNET_HOME} + $ENV{LIBICNETHOME} + $ENV{CCNX_HOME} + $ENV{PARC_HOME} + $ENV{FOUNDATION_HOME} + /usr/local/parc + /usr/local/ccnx + /usr/local/ccn + /usr/local + /opt + /usr + ) + +find_path(LIBICNET_INCLUDE_DIR icnet/icnet_common.h + HINTS ${LIBICNET_SEARCH_PATH_LIST} + PATH_SUFFIXES include + DOC "Find the libicnet includes") + +find_library(LIBICNET_LIBRARY NAMES icnet + HINTS ${LIBICNET_SEARCH_PATH_LIST} + PATH_SUFFIXES lib + DOC "Find the libicnet libraries") + +set(LIBICNET_LIBRARIES ${LIBICNET_LIBRARY}) +set(LIBICNET_INCLUDE_DIRS ${LIBICNET_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Libicnet DEFAULT_MSG LIBICNET_LIBRARY LIBICNET_INCLUDE_DIR) diff --git a/cmake/Modules/version.cmake b/cmake/Modules/version.cmake new file mode 100644 index 00000000..44a41099 --- /dev/null +++ b/cmake/Modules/version.cmake @@ -0,0 +1,15 @@ +# +# Get a version to pass on the command line +# +execute_process(COMMAND ${PROJECT_SOURCE_DIR}/cmake/get_version.sh ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE RELEASE_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE) + +execute_process(COMMAND date -u +%Y-%m-%dT%H:%M:%SZ + OUTPUT_VARIABLE ISO_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE) + +MESSAGE(STATUS "Configuring version ${RELEASE_VERSION}") + +add_definitions("-DRELEASE_VERSION=\"${RELEASE_VERSION}\"") + diff --git a/http-server/common.h b/http-server/common.h new file mode 100644 index 00000000..e69706e6 --- /dev/null +++ b/http-server/common.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ICN_WEB_SERVER_COMMON_H_ +#define ICN_WEB_SERVER_COMMON_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// ICN extensions +#include + +typedef boost::asio::ip::tcp::socket socket_type; +typedef std::function SendCallback; + +#endif // ICN_WEB_SERVER_COMMON_H_ diff --git a/http-server/configuration.cc b/http-server/configuration.cc new file mode 100644 index 00000000..8f205a0a --- /dev/null +++ b/http-server/configuration.cc @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "configuration.h" + +namespace icn_httpserver { + +Configuration::Configuration(unsigned short port, size_t num_threads) + : num_threads_(num_threads), port_(port), reuse_address_(true) { +} + +size_t Configuration::getNum_threads() const { + return num_threads_; +} + +void Configuration::setNum_threads(size_t num_threads) { + Configuration::num_threads_ = num_threads; +} + +unsigned short Configuration::getPort() const { + return port_; +} + +void Configuration::setPort(unsigned short port) { + Configuration::port_ = port; +} + +const std::string &Configuration::getAddress() const { + return address_; +} + +void Configuration::setAddress(const std::string &address) { + Configuration::address_ = address; +} + +bool Configuration::isReuse_address() const { + return reuse_address_; +} + +void Configuration::setReuse_address(bool reuse_address) { + Configuration::reuse_address_ = reuse_address; +} + +} // end namespace icn_httpserver \ No newline at end of file diff --git a/http-server/configuration.h b/http-server/configuration.h new file mode 100644 index 00000000..65d3170a --- /dev/null +++ b/http-server/configuration.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ICN_WEB_SERVER_CONFIGURATION_H_ +#define ICN_WEB_SERVER_CONFIGURATION_H_ + +#include "common.h" + +namespace icn_httpserver { + +class Configuration { + public: + Configuration(unsigned short port, size_t num_threads); + + size_t getNum_threads() const; + + void setNum_threads(size_t num_threads); + + unsigned short getPort() const; + + void setPort(unsigned short port); + + const std::string &getAddress() const; + + void setAddress(const std::string &address); + + bool isReuse_address() const; + + void setReuse_address(bool reuse_address); + + private: + size_t num_threads_; + unsigned short port_; + std::string address_; + bool reuse_address_; +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_CONFIGURATION_H_ diff --git a/http-server/content.cc b/http-server/content.cc new file mode 100644 index 00000000..f6bd0483 --- /dev/null +++ b/http-server/content.cc @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "content.h" + +namespace icn_httpserver { + +Content::Content(boost::asio::streambuf &streambuf) + : std::istream(&streambuf), streambuf_(streambuf) { +} + +std::size_t Content::size() { + return streambuf_.size(); +} + +std::string Content::string() { + std::stringstream ss; + ss << rdbuf(); + return ss.str(); +} + +} // end namespace icn_httpserver \ No newline at end of file diff --git a/http-server/content.h b/http-server/content.h new file mode 100644 index 00000000..a81ad643 --- /dev/null +++ b/http-server/content.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common.h" + +#ifndef ICN_WEB_SERVER_CONTENT_H_ +#define ICN_WEB_SERVER_CONTENT_H_ + +namespace icn_httpserver { + +class Content + : public std::istream { + public: + Content(boost::asio::streambuf &streambuf); + + size_t size(); + + std::string string(); + + private: + boost::asio::streambuf &streambuf_; +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_CONTENT_H_ diff --git a/http-server/http_server.cc b/http-server/http_server.cc new file mode 100644 index 00000000..ebc8503b --- /dev/null +++ b/http-server/http_server.cc @@ -0,0 +1,487 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Ole Christian Eidheim + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "http_server.h" + +namespace icn_httpserver { + +HttpServer::HttpServer(unsigned short port, + std::string icn_name, size_t num_threads, long timeout_request, long timeout_send_or_receive) + : config_(port, num_threads), + icn_name_(icn_name), + internal_io_service_(std::make_shared()), + io_service_(*internal_io_service_), + acceptor_(io_service_), + acceptor_producer_(std::make_shared(icnet::Name(icn_name))), + timeout_request_(timeout_request), + timeout_content_(timeout_send_or_receive) { +} + +HttpServer::HttpServer(unsigned short port, + std::string icn_name, + size_t num_threads, + long timeout_request, + long timeout_send_or_receive, + boost::asio::io_service &ioService) + : config_(port, num_threads), + icn_name_(icn_name), + io_service_(ioService), + acceptor_(io_service_), + acceptor_producer_(std::make_shared(icnet::Name(icn_name))), + timeout_request_(timeout_request), + timeout_content_(timeout_send_or_receive) { +} + +void HttpServer::processIncomingInterest(icnet::ProducerSocket &p, const icnet::Interest &interest) { + icnet::Name complete_name = interest.getName(); + + if (complete_name.getSegmentCount() <= 2) { + std::cerr << "Received malformed name " << complete_name << ". Ignoring it." << std::endl; + return; + } + + icnet::Name request_name = complete_name.get(-1).isSegment() ? complete_name.getPrefix(-1) : complete_name; + + std::unique_lock lock(thread_list_mtx_); + if (icn_producers_.size() < config_.getNum_threads()) { + if (icn_producers_.find(request_name) == icn_producers_.end()) { + std::cout << "Received interest name: " << request_name << std::endl; + std::shared_ptr p = makeProducer(request_name); + icn_producers_[request_name] = p; + std::cout << "Starting new thread" << std::endl; + std::thread t([this, interest, request_name, p]() { + processInterest(request_name, p); + }); + t.detach(); + } else { + icn_producers_[request_name]->onInterest(complete_name, interest); + } + } +} + +void HttpServer::signPacket(icnet::ProducerSocket &p, icnet::ContentObject &content_object) { + // This is not really signing the packet. Signing every packet is cpu expensive. + icnet::KeyLocator keyLocator; + content_object.signWithSha256(keyLocator); +} + +void HttpServer::processInterest(icnet::Name request_name, std::shared_ptr p) { + // Create timer + std::shared_ptr portal; + p->getSocketOption(icnet::GeneralTransportOptions::PORTAL, portal); + boost::asio::io_service &ioService = portal->getIoService(); + + boost::asio::deadline_timer t(ioService, boost::posix_time::seconds(5)); + + std::function + wait_callback = [&ioService](const boost::system::error_code e) { + if (!e) { + // Be sure to delete the timer before the io_service, otherwise we'll get some strange behavior! + ioService.stop(); + } + }; + + t.async_wait(wait_callback); + + // Get the name of the HTTP method to compute + std::string method = request_name.get(1).toString(); + std::transform(method.begin(), method.end(), method.begin(), ::toupper); + std::string path; + + // This is done for getting rid of useless name components such as ccnx: or ndn: + if (request_name.getSegmentCount() > 2) { + std::string rawPath = request_name.getSubName(2).toString(); + std::size_t pos = rawPath.find("/"); + path = rawPath.substr(pos); + } + + std::function + interest_enter_callback = [this, &wait_callback, &t](icnet::ProducerSocket &p, const icnet::Interest &interest) { + t.cancel(); + t.expires_from_now(boost::posix_time::seconds(5)); + t.async_wait(wait_callback); + }; + + p->setSocketOption(icnet::ProducerCallbacksOptions::INTEREST_INPUT, + (icnet::ProducerInterestCallback) interest_enter_callback); + + // TODO The parsing of the parameters in theURL is missing! + if (method == GET) { + // Build new GET request to submit to the server + + std::shared_ptr request = std::make_shared(p, request_name.toString(), path, method, "1.0"); + + std::static_pointer_cast(request)->getHeader() + .insert(std::make_pair(std::string("Host"), std::string("localhost"))); + + p->attach(); + + find_resource(nullptr, request); + } + + p->serveForever(); + + std::unique_lock lock(thread_list_mtx_); + icn_producers_.erase(request_name); +} + +std::shared_ptr HttpServer::makeProducer(icnet::Name request_name) { + std::shared_ptr producer = std::make_shared(request_name); + // producer->setContextOption(FAST_SIGNING, true); + // producer->setContextOption(DATA_TO_SECURE, (api::ProducerDataCallback) bind(&http-server::signPacket, this, _1, _2)); + producer->setSocketOption(icnet::GeneralTransportOptions::DATA_PACKET_SIZE, PACKET_SIZE); + producer->setSocketOption(icnet::GeneralTransportOptions::OUTPUT_BUFFER_SIZE, SEND_BUFFER_SIZE); + + return producer; +} + +void HttpServer::setIcnAcceptor() { + acceptor_producer_->setSocketOption(icnet::ProducerCallbacksOptions::INTEREST_INPUT, + (icnet::ProducerInterestCallback) bind(&HttpServer::processIncomingInterest, + this, + std::placeholders::_1, + std::placeholders::_2)); + acceptor_producer_->dispatch(); +} + +void HttpServer::spawnTcpThreads() { + if (io_service_.stopped()) { + io_service_.reset(); + } + + boost::asio::ip::tcp::endpoint endpoint; + + if (config_.getAddress().size() > 0) { + endpoint = + boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(config_.getAddress()), config_.getPort()); + } else { + endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config_.getPort()); + } + + acceptor_.open(endpoint.protocol()); + acceptor_.set_option(boost::asio::socket_base::reuse_address(config_.isReuse_address())); + acceptor_.bind(endpoint); + acceptor_.listen(); + + accept(); + + //If num_threads>1, start m_io_service.run() in (num_threads-1) threads for thread-pooling + socket_threads_.clear(); + for (size_t c = 1; c < config_.getNum_threads(); c++) { + socket_threads_.emplace_back([this]() { + io_service_.run(); + }); + } +} + +void HttpServer::start() { + //Copy the resources to opt_resource for more efficient request processing + opt_resource_.clear(); + for (auto &res: resource) { + for (auto &res_method: res.second) { + auto it = opt_resource_.end(); + for (auto opt_it = opt_resource_.begin(); opt_it != opt_resource_.end(); opt_it++) { + if (res_method.first == opt_it->first) { + it = opt_it; + break; + } + } + if (it == opt_resource_.end()) { + opt_resource_.emplace_back(); + it = opt_resource_.begin() + (opt_resource_.size() - 1); + it->first = res_method.first; + } + it->second.emplace_back(boost::regex(res.first), res_method.second); + } + } + + spawnTcpThreads(); + setIcnAcceptor(); + + + + // Wait for the rest of the threads, if any, to finish as well + for (auto &t: socket_threads_) { + t.join(); + } + + // for (auto &t : icn_threads) { + // t.second.get(); + // } +} + +void HttpServer::stop() { + acceptor_.close(); + acceptor_producer_.reset(); + io_service_.stop(); + + for (auto p: icn_producers_) { + std::shared_ptr portalPtr; + p.second->getSocketOption(icnet::GeneralTransportOptions::PORTAL, portalPtr); + portalPtr->getIoService().stop(); + } + + for (auto p : icn_producers_) { + p.second.reset(); + } + +} + +void HttpServer::accept() { + //Create new socket for this connection + //Shared_ptr is used to pass temporary objects to the asynchronous functions + std::shared_ptr socket = std::make_shared(io_service_); + + acceptor_.async_accept(*socket, [this, socket](const boost::system::error_code &ec) { + //Immediately start accepting a new connection + accept(); + + if (!ec) { + boost::asio::ip::tcp::no_delay option(true); + socket->set_option(option); + read_request_and_content(socket); + } + }); +} + +void HttpServer::send(std::shared_ptr response, SendCallback callback) const { + response->send(callback); +} + +std::shared_ptr HttpServer::set_timeout_on_socket(std::shared_ptr socket, + long seconds) { + std::shared_ptr timer = std::make_shared(io_service_); + timer->expires_from_now(boost::posix_time::seconds(seconds)); + timer->async_wait([socket](const boost::system::error_code &ec) { + if (!ec) { + boost::system::error_code ec; + socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + socket->lowest_layer().close(); + } + }); + return timer; +} + +void HttpServer::read_request_and_content(std::shared_ptr socket) { + // Create new streambuf (Request::streambuf) for async_read_until() + // shared_ptr is used to pass temporary objects to the asynchronous functions + std::shared_ptr request = std::make_shared(); + request->read_remote_endpoint_data(*socket); + + //Set timeout on the following boost::asio::async-read or write function + std::shared_ptr timer; + if (timeout_request_ > 0) { + timer = set_timeout_on_socket(socket, timeout_request_); + } + + boost::asio::async_read_until(*socket, + request->getStreambuf(), + "\r\n\r\n", + [this, socket, request, timer](const boost::system::error_code &ec, + size_t bytes_transferred) { + if (timeout_request_ > 0) { + timer->cancel(); + } + if (!ec) { + //request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs: + //"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter" + //The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the + //streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content). + size_t num_additional_bytes = request->getStreambuf().size() - bytes_transferred; + + if (!parse_request(request, request->getContent())) { + return; + } + + //If content, read that as well + auto it = request->getHeader().find("Content-Length"); + if (it != request->getHeader().end()) { + //Set timeout on the following boost::asio::async-read or write function + std::shared_ptr timer; + if (timeout_content_ > 0) { + timer = set_timeout_on_socket(socket, timeout_content_); + } + unsigned long long content_length; + try { + content_length = stoull(it->second); + } catch (const std::exception &) { + return; + } + if (content_length > num_additional_bytes) { + boost::asio::async_read(*socket, + request->getStreambuf(), + boost::asio::transfer_exactly( + content_length - num_additional_bytes), + [this, socket, request, timer](const boost::system::error_code &ec, + size_t /*bytes_transferred*/) { + if (timeout_content_ > 0) { + timer->cancel(); + } + if (!ec) { + find_resource(socket, request); + } + }); + } else { + + if (timeout_content_ > 0) { + timer->cancel(); + } + + find_resource(socket, request); + } + } else { + find_resource(socket, request); + } + } + }); +} + +bool HttpServer::parse_request(std::shared_ptr request, std::istream &stream) const { + std::string line; + getline(stream, line); + size_t method_end; + if ((method_end = line.find(' ')) != std::string::npos) { + size_t path_end; + if ((path_end = line.find(' ', method_end + 1)) != std::string::npos) { + request->setMethod(line.substr(0, method_end)); + request->setPath(line.substr(method_end + 1, path_end - method_end - 1)); + + size_t protocol_end; + if ((protocol_end = line.find('/', path_end + 1)) != std::string::npos) { + if (line.substr(path_end + 1, protocol_end - path_end - 1) != "HTTP") { + return false; + } + request->setHttp_version(line.substr(protocol_end + 1, line.size() - protocol_end - 2)); + } else { + return false; + } + + getline(stream, line); + size_t param_end; + while ((param_end = line.find(':')) != std::string::npos) { + size_t value_start = param_end + 1; + if ((value_start) < line.size()) { + if (line[value_start] == ' ') { + value_start++; + } + if (value_start < line.size()) { + request->getHeader().insert(std::make_pair(line.substr(0, param_end), + line.substr(value_start, line.size() - value_start - 1))); + } + } + + getline(stream, line); + } + } else { + return false; + } + } else { + return false; + } + return true; +} + +void HttpServer::find_resource(std::shared_ptr socket, std::shared_ptr request) { + //Find path- and method-match, and call write_response + for (auto &res: opt_resource_) { + if (request->getMethod() == res.first) { + for (auto &res_path: res.second) { + boost::smatch sm_res; + if (boost::regex_match(request->getPath(), sm_res, res_path.first)) { + request->setPath_match(std::move(sm_res)); + write_response(socket, request, res_path.second); + return; + } + } + } + } + auto it_method = default_resource.find(request->getMethod()); + if (it_method != default_resource.end()) { + write_response(socket, request, it_method->second); + return; + } + + std::cout << "resource not found" << std::endl; +} + +void HttpServer::write_response(std::shared_ptr socket, + std::shared_ptr request, + ResourceCallback &resource_function) { + //Set timeout on the following boost::asio::async-read or write function + std::shared_ptr timer; + if (timeout_content_ > 0 && socket) { + timer = set_timeout_on_socket(socket, timeout_content_); + } + + Response *resp; + + if (socket) { + resp = new SocketResponse(socket); + } else { + resp = new IcnResponse(std::static_pointer_cast(request)->getProducer(), + std::static_pointer_cast(request)->getName(), + std::static_pointer_cast(request)->getPath(), + std::static_pointer_cast(request)->getRequest_id()); + } + + auto response = std::shared_ptr(resp, [this, request, timer, socket](Response *response_ptr) { + + auto response = std::shared_ptr(response_ptr); + response->setIsLast(true); + + send(response, [this, response, request, timer, socket](const boost::system::error_code &ec) { + if (!ec) { + if (socket && timeout_content_ > 0) { + timer->cancel(); + } + + float http_version; + try { + http_version = stof(request->getHttp_version()); + } catch (const std::exception &) { + return; + } + + auto range = request->getHeader().equal_range("Connection"); + for (auto it = range.first; it != range.second; it++) { + if (boost::iequals(it->second, "close")) { + return; + } + } + if (http_version > 1.05) { + read_request_and_content(std::static_pointer_cast(response)->getSocket()); + } + } + }); + }); + + try { + resource_function(response, request); + } catch (const std::exception &) { + return; + } +} + +} // end namespace icn_httpserver + diff --git a/http-server/http_server.h b/http-server/http_server.h new file mode 100644 index 00000000..fbc841c7 --- /dev/null +++ b/http-server/http_server.h @@ -0,0 +1,125 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Ole Christian Eidheim + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ICN_WEB_SERVER_WEB_SERVER_H_ +#define ICN_WEB_SERVER_WEB_SERVER_H_ + +#include "common.h" +#include "icn_request.h" +#include "icn_response.h" +#include "socket_request.h" +#include "socket_response.h" +#include "configuration.h" + +typedef std::function, std::shared_ptr)> + ResourceCallback; + +#define SERVER_NAME "/webserver" +#define PACKET_SIZE 1500 +#define SEND_BUFFER_SIZE 30000 + +#define GET "GET" +#define POST "POST" +#define PUT "PUT" +#define DELETE "DELETE" +#define PATCH "PATCH" + +namespace icn_httpserver { + +class HttpServer { + public: + explicit HttpServer(unsigned short port, + std::string icn_name, + size_t num_threads, + long timeout_request, + long timeout_send_or_receive); + + explicit HttpServer(unsigned short port, + std::string icn_name, + size_t num_threads, + long timeout_request, + long timeout_send_or_receive, + boost::asio::io_service &ioService); + + void start(); + + void stop(); + + void accept(); + + void send(std::shared_ptr response, SendCallback callback = nullptr) const; + + std::unordered_map > resource; + std::unordered_map default_resource; + + private: + void processInterest(icnet::Name request_name, std::shared_ptr p); + + void processIncomingInterest(icnet::ProducerSocket &p, const icnet::Interest &interest); + + void signPacket(icnet::ProducerSocket &p, icnet::ContentObject &content_object); + + void spawnTcpThreads(); + + void setIcnAcceptor(); + + std::shared_ptr set_timeout_on_socket(std::shared_ptr socket, long seconds); + + void read_request_and_content(std::shared_ptr socket); + + bool parse_request(std::shared_ptr request, std::istream &stream) const; + + void find_resource(std::shared_ptr socket, std::shared_ptr request); + + void write_response(std::shared_ptr socket, + std::shared_ptr request, + ResourceCallback &resource_function); + + std::shared_ptr makeProducer(icnet::Name request_name); + + Configuration config_; + + std::vector > > > opt_resource_; + + std::shared_ptr internal_io_service_; + boost::asio::io_service &io_service_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector socket_threads_; + + // ICN parameters + std::string icn_name_; + std::shared_ptr acceptor_producer_; + std::unordered_map> icn_threads_; + std::unordered_map> icn_producers_; + std::unordered_map> name_io_service_map_; + std::mutex thread_list_mtx_; + + long timeout_request_; + long timeout_content_; + +}; + +} // end namespace icn_httpserver + +#endif //ICN_WEB_SERVER_WEB_SERVER_H_ diff --git a/http-server/icn_request.cc b/http-server/icn_request.cc new file mode 100644 index 00000000..07d95a22 --- /dev/null +++ b/http-server/icn_request.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "icn_request.h" + +namespace icn_httpserver { + +IcnRequest::IcnRequest(std::shared_ptr producer) + : producer_(producer) { + time_t t; + time(&t); + srand((unsigned int) t); + request_id_ = rand(); +} + +IcnRequest::IcnRequest(std::shared_ptr producer, + std::string name, + std::string path, + std::string method, std::string http_version) + : IcnRequest(producer) { + this->name_ = name; + this->path_ = path; + this->method_ = method; + this->http_version_ = http_version; +} + +const std::string &IcnRequest::getName() const { + return name_; +} + +void IcnRequest::setName(const std::string &name) { + IcnRequest::name_ = name; +} + +int IcnRequest::getRequest_id() const { + return request_id_; +} + +void IcnRequest::setRequest_id(int request_id) { + IcnRequest::request_id_ = request_id; +} + +const std::shared_ptr &IcnRequest::getProducer() const { + return producer_; +} + +void IcnRequest::setProducer(const std::shared_ptr &producer) { + IcnRequest::producer_ = producer; +} + +} // end namespace icn_httpserver diff --git a/http-server/icn_request.h b/http-server/icn_request.h new file mode 100644 index 00000000..c5aa10e4 --- /dev/null +++ b/http-server/icn_request.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ICN_WEB_SERVER_ICNREQUEST_H_ +#define ICN_WEB_SERVER_ICNREQUEST_H_ + +#include "common.h" +#include "request.h" + +namespace icn_httpserver { + +class IcnRequest + : public Request { + public: + IcnRequest(std::shared_ptr producer); + + IcnRequest(std::shared_ptr producer, + std::string name, + std::string path, + std::string method, + std::string http_version); + + const std::string &getName() const; + + void setName(const std::string &name); + + int getRequest_id() const; + + void setRequest_id(int request_id); + + const std::shared_ptr &getProducer() const; + + void setProducer(const std::shared_ptr &producer); + + private: + std::string name_; + int request_id_; + std::shared_ptr producer_; + +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_ICNREQUEST_H_ diff --git a/http-server/icn_response.cc b/http-server/icn_response.cc new file mode 100644 index 00000000..6fe8b1e1 --- /dev/null +++ b/http-server/icn_response.cc @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "icn_response.h" + +namespace icn_httpserver { + +IcnResponse::IcnResponse(std::shared_ptr producer, + std::string ndn_name, + std::string ndn_path, + int response_id) + : producer_(producer), ndn_name_(ndn_name), ndn_path_(ndn_path), response_id_(response_id) { +} + +void IcnResponse::send(const SendCallback &callback) { + std::size_t buffer_size = this->streambuf_.size(); + this->streambuf_.commit(this->streambuf_.size()); + + this->producer_->produce(icnet::Name(/*this->ndn_name*/), + boost::asio::buffer_cast(this->streambuf_.data()), + buffer_size, + this->response_id_, + this->is_last_); + + this->streambuf_.consume(buffer_size); + + if (callback) { + callback(boost::system::error_code()); + } +} + +} // end namespace icn_httpserver diff --git a/http-server/icn_response.h b/http-server/icn_response.h new file mode 100644 index 00000000..e9af0d40 --- /dev/null +++ b/http-server/icn_response.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "response.h" + +#ifndef ICN_WEB_SERVER_ICNRESPONSE_H_ +#define ICN_WEB_SERVER_ICNRESPONSE_H_ + +namespace icn_httpserver { + +class IcnResponse + : public Response { + + public: + + IcnResponse(std::shared_ptr producer, + std::string ndn_name, + std::string ndn_path, + int response_id); + + void send(const SendCallback &callback = nullptr); + + private: + std::string ndn_name_; + std::string ndn_path_; + int response_id_; + std::shared_ptr producer_; +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_ICNRESPONSE_H_ diff --git a/http-server/request.cc b/http-server/request.cc new file mode 100644 index 00000000..0e726dc8 --- /dev/null +++ b/http-server/request.cc @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "request.h" + +namespace icn_httpserver { + +Request::Request() + : content_(streambuf_) { +} + +const std::string &Request::getMethod() const { + return method_; +} + +void Request::setMethod(const std::string &method) { + Request::method_ = method; +} + +const std::string &Request::getPath() const { + return path_; +} + +void Request::setPath(const std::string &path) { + Request::path_ = path; +} + +const std::string &Request::getHttp_version() const { + return http_version_; +} + +void Request::setHttp_version(const std::string &http_version) { + Request::http_version_ = http_version; +} + +std::unordered_multimap &Request::getHeader() { + return header_; +} + +Content &Request::getContent() { + return content_; +} + +const boost::smatch &Request::getPath_match() const { + return path_match_; +} + +void Request::setPath_match(const boost::smatch &path_match) { + Request::path_match_ = path_match; +} + +} // end namespace icn_httpserver diff --git a/http-server/request.h b/http-server/request.h new file mode 100644 index 00000000..6ffff296 --- /dev/null +++ b/http-server/request.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ICN_WEB_SERVER_REQUEST_H_ +#define ICN_WEB_SERVER_REQUEST_H_ + +#include "common.h" +#include "content.h" + +namespace icn_httpserver { + +class iequal_to { + public: + bool operator()(const std::string &key1, const std::string &key2) const { + return boost::algorithm::iequals(key1, key2); + } +}; + +class ihash { + public: + size_t operator()(const std::string &key) const { + std::size_t seed = 0; + for (auto &c: key) + boost::hash_combine(seed, std::tolower(c)); + return seed; + } +}; + +class Request { + public: + + Request(); + + virtual void read_remote_endpoint_data(socket_type &socket) { + }; + + const std::string &getMethod() const; + + void setMethod(const std::string &method); + + const std::string &getPath() const; + + void setPath(const std::string &path); + + const std::string &getHttp_version() const; + + void setHttp_version(const std::string &http_version); + + std::unordered_multimap &getHeader(); + + boost::asio::streambuf &getStreambuf() { + return streambuf_; + } + + Content &getContent(); + + const boost::smatch &getPath_match() const; + + void setPath_match(const boost::smatch &path_match); + + protected: + std::string method_, path_, http_version_; + Content content_; + std::unordered_multimap header_; + boost::smatch path_match_; + boost::asio::streambuf streambuf_; +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_REQUEST_H_ diff --git a/http-server/response.cc b/http-server/response.cc new file mode 100644 index 00000000..b322cad8 --- /dev/null +++ b/http-server/response.cc @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common.h" +#include "response.h" + +namespace icn_httpserver { + +Response::Response() + : std::ostream(&streambuf_), is_last_(false) { +} + +Response::~Response() { +} + +std::size_t Response::size() { + return streambuf_.size(); +} + +bool Response::isIsLast() const { + return is_last_; +} + +void Response::setIsLast(bool is_last) { + Response::is_last_ = is_last; +} + +void Response::setResponseLength(std::size_t length) { + response_length_ = length; +} + +} // end namespace icn_httpserver diff --git a/http-server/response.h b/http-server/response.h new file mode 100644 index 00000000..649bcea3 --- /dev/null +++ b/http-server/response.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ICN_WEB_SERVER_RESPONSE_H_ +#define ICN_WEB_SERVER_RESPONSE_H_ + +#include "common.h" + +namespace icn_httpserver { + +class Response + : public std::ostream { + public: + Response(); + + virtual + ~Response(); + + size_t size(); + + virtual void send(const SendCallback &callback = nullptr) { + }; + + bool isIsLast() const; + + void setIsLast(bool is_last); + + void setResponseLength(std::size_t length); + + protected: + boost::asio::streambuf streambuf_; + bool is_last_; + std::size_t response_length_; +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_RESPONSE_H_ diff --git a/http-server/socket_request.cc b/http-server/socket_request.cc new file mode 100644 index 00000000..10c663a5 --- /dev/null +++ b/http-server/socket_request.cc @@ -0,0 +1,37 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Ole Christian Eidheim + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "socket_request.h" + +namespace icn_httpserver { + +void SocketRequest::read_remote_endpoint_data(socket_type &socket) { + try { + remote_endpoint_address_ = socket.lowest_layer().remote_endpoint().address().to_string(); + remote_endpoint_port_ = socket.lowest_layer().remote_endpoint().port(); + } catch (const std::exception &) { + } +} + +} // end namespace icn_httpserver diff --git a/http-server/socket_request.h b/http-server/socket_request.h new file mode 100644 index 00000000..cc77ef73 --- /dev/null +++ b/http-server/socket_request.h @@ -0,0 +1,44 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Ole Christian Eidheim + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ICN_WEB_SERVER_SOCKETREQUEST_H_ +#define ICN_WEB_SERVER_SOCKETREQUEST_H_ + +#include "request.h" + +namespace icn_httpserver { + +class SocketRequest + : public Request { + public: + void read_remote_endpoint_data(socket_type &socket); + + private: + std::string remote_endpoint_address_; + unsigned short remote_endpoint_port_; +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_SOCKETREQUEST_H_ diff --git a/http-server/socket_response.cc b/http-server/socket_response.cc new file mode 100644 index 00000000..ec33820e --- /dev/null +++ b/http-server/socket_response.cc @@ -0,0 +1,54 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Ole Christian Eidheim + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "socket_response.h" + +namespace icn_httpserver { + +SocketResponse::SocketResponse(std::shared_ptr socket) + : socket_(socket) { +} + +SocketResponse::~SocketResponse() { +}; + +void SocketResponse::send(const SendCallback &callback) { + boost::asio::async_write(*this->socket_, + this->streambuf_, + [this, callback](const boost::system::error_code &ec, size_t /*bytes_transferred*/) { + if (callback) { + callback(ec); + } + }); +} + +const std::shared_ptr &SocketResponse::getSocket() const { + return socket_; +} + +void SocketResponse::setSocket(const std::shared_ptr &socket) { + SocketResponse::socket_ = socket; +} + +} // end namespace icn_httpserver diff --git a/http-server/socket_response.h b/http-server/socket_response.h new file mode 100644 index 00000000..722e8196 --- /dev/null +++ b/http-server/socket_response.h @@ -0,0 +1,52 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Ole Christian Eidheim + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ICN_WEB_SERVER_SOCKETRESPONSE_H_ +#define ICN_WEB_SERVER_SOCKETRESPONSE_H_ + +#include "response.h" + +namespace icn_httpserver { + +class SocketResponse + : public Response { + public: + + SocketResponse(std::shared_ptr socket); + + ~SocketResponse(); + + void send(const SendCallback &callback = nullptr); + + const std::shared_ptr &getSocket() const; + + void setSocket(const std::shared_ptr &socket); + + private: + std::shared_ptr socket_; +}; + +} // end namespace icn_httpserver + +#endif // ICN_WEB_SERVER_SOCKETRESPONSE_H_ diff --git a/main.cc b/main.cc new file mode 100644 index 00000000..13e4ee73 --- /dev/null +++ b/main.cc @@ -0,0 +1,215 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Ole Christian Eidheim + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include "http-server/http_server.h" + +typedef icn_httpserver::HttpServer HttpServer; +typedef icn_httpserver::Response Response; +typedef icn_httpserver::Request Request; + +namespace std { + +void default_resource_send(const HttpServer &server, + shared_ptr response, + shared_ptr ifs, + shared_ptr> buffer, + std::size_t bytes_to_read) { + streamsize read_length; + + if ((read_length = ifs->read(&(*buffer)[0], buffer->size()).gcount()) > 0) { + response->write(&(*buffer)[0], read_length); + + if (bytes_to_read <= static_cast(buffer->size())) { + // If this is the last part of the response, send it at the pointer deletion! + return; + } + + std::size_t to_read = bytes_to_read - read_length; + server.send(response, [&server, response, ifs, buffer, to_read](const boost::system::error_code &ec) { + if (!ec) { + default_resource_send(server, response, ifs, buffer, to_read); + } else { + cerr << "Connection interrupted" << endl; + } + }); + } +} + +void afterSignal(HttpServer *webServer, const boost::system::error_code &errorCode) { + cout << "\nGracefully terminating http-server... wait." << endl; + webServer->stop(); +} + +void usage(const char *programName) { + cerr << programName << " [-p PATH_TO_ROOT_FOOT_FOLDER] [-l WEBSERVER_PREFIX]\n" + << "Web server able to publish content and generate http responses over TCP/ICN\n" << endl; + + exit(1); +} + +int main(int argc, char **argv) { + // Parse command line arguments + + string root_folder = "/var/www/html"; + string webserver_prefix = "ccnx:/webserver"; + + int opt = 0; + + while ((opt = getopt(argc, argv, "p:l:h")) != -1) { + + switch (opt) { + case 'p': + root_folder = optarg; + break; + case 'l': + webserver_prefix = optarg; + break; + case 'h': + usage(argv[0]); + break; + } + } + + if (!boost::filesystem::exists(boost::filesystem::path(root_folder))) { + + // Try to create it + try { + if (!boost::filesystem::create_directories(boost::filesystem::path(root_folder))) { + throw boost::filesystem::filesystem_error("", boost::system::error_code()); + } + } catch (boost::filesystem::filesystem_error) { + std::cerr << "The web root folder " << root_folder << " does not exist and its creation failed. Exiting.." + << std::endl; + return (EXIT_FAILURE); + } + } + + std::cout << "Using web root folder: [" << root_folder << "]" << std::endl; + std::cout << "Using locator: [" << webserver_prefix << "]" << std::endl; + + boost::asio::io_service io_service; + HttpServer server(8080, webserver_prefix, 50, 5, 300, io_service); + + // GET for the path /info + // Responds with some server info + server.resource["^/info$"]["GET"] = [](shared_ptr response, shared_ptr request) { + stringstream content_stream; + content_stream << "

This webserver is able to reply to HTTP over TCP/ICN

"; + content_stream << request->getMethod() << " " << request->getPath() << " HTTP/" << request->getHttp_version() + << "
"; + + for (auto &header: request->getHeader()) { + content_stream << header.first << ": " << header.second << "
"; + } + + //find length of content_stream (length received using content_stream.tellp()) + content_stream.seekp(0, ios::end); + + *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" + << content_stream.rdbuf(); + }; + + // Default GET-example. If no other matches, this anonymous function will be called. + // Will respond with content in the web/-directory, and its subdirectories. + // Default file: index.html + // Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server + server.default_resource["GET"] = [&server, &root_folder](shared_ptr response, shared_ptr request) { + const auto web_root_path = boost::filesystem::canonical(root_folder); + + boost::filesystem::path path = web_root_path; + path /= request->getPath(); + + if (boost::filesystem::exists(path)) { + + path = boost::filesystem::canonical(path); + + //Check if path is within web_root_path + if (distance(web_root_path.begin(), web_root_path.end()) <= distance(path.begin(), path.end()) + && equal(web_root_path.begin(), web_root_path.end(), path.begin())) { + + if (boost::filesystem::is_directory(path)) { + path /= "index.html"; + } // default path + + if (boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path)) { + + auto ifs = make_shared(); + ifs->open(path.string(), ifstream::in | ios::binary); + + if (*ifs) { + //read and send 1 MB at a time + streamsize buffer_size = 15 * 1024 * 1024; + auto buffer = make_shared < vector < char > > (buffer_size); + + ifs->seekg(0, ios::end); + auto length = ifs->tellg(); + ifs->seekg(0, ios::beg); + + response->setResponseLength(length); + + icn_httpserver::SocketRequest + *socket_request = dynamic_cast(request.get()); + + if (socket_request) { + *response << "HTTP/1.0 200 OK\r\nContent-Length: " << length << "\r\n\r\n"; + } + + default_resource_send(server, response, ifs, buffer, length); + + return; + } + } + } + } + + string content = "Could not open path " + request->getPath(); + + *response << "HTTP/1.1 404 Not found\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; + + }; + + // Let the main thread to catch SIGINT and SIGQUIT + boost::asio::signal_set signals(io_service, SIGINT, SIGQUIT); + signals.async_wait(bind(afterSignal, &server, placeholders::_1)); + + thread server_thread([&server]() { + //Start server + server.start(); + }); + + server_thread.join(); + + return 0; +} + +} // end namespace std + + +int main(int argc, char **argv) { + return std::main(argc, argv); +} -- cgit 1.2.3-korg