Commit 669dee22 authored by Kai-Uwe Sattler's avatar Kai-Uwe Sattler
Browse files

initial commit

parents
# - Find JeMalloc library
# Find the native JeMalloc includes and library
# This module defines
# JEMALLOC_INCLUDE_DIRS, where to find jemalloc.h, Set when
# JEMALLOC_INCLUDE_DIR is found.
# JEMALLOC_LIBRARIES, libraries to link against to use JeMalloc.
# JEMALLOC_ROOT_DIR, The base directory to search for JeMalloc.
# This can also be an environment variable.
# JEMALLOC_FOUND, If false, do not try to use JeMalloc.
#
# also defined, but not for general use are
# JEMALLOC_LIBRARY, where to find the JeMalloc library.
#=============================================================================
# Copyright 2011 Blender Foundation.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# If JEMALLOC_ROOT_DIR was defined in the environment, use it.
IF(NOT JEMALLOC_ROOT_DIR AND NOT $ENV{JEMALLOC_ROOT_DIR} STREQUAL "")
SET(JEMALLOC_ROOT_DIR $ENV{JEMALLOC_ROOT_DIR})
ENDIF()
SET(_jemalloc_SEARCH_DIRS
${JEMALLOC_ROOT_DIR}
/usr/local
/sw # Fink
/opt/local # DarwinPorts
/opt/csw # Blastwave
)
FIND_PATH(JEMALLOC_INCLUDE_DIR
NAMES
jemalloc.h
HINTS
${_jemalloc_SEARCH_DIRS}
PATH_SUFFIXES
include/jemalloc
)
FIND_LIBRARY(JEMALLOC_LIBRARY
NAMES
jemalloc
HINTS
${_jemalloc_SEARCH_DIRS}
PATH_SUFFIXES
lib64 lib
)
# handle the QUIETLY and REQUIRED arguments and set JEMALLOC_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(JeMalloc DEFAULT_MSG
JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR)
IF(JEMALLOC_FOUND)
SET(JEMALLOC_LIBRARIES ${JEMALLOC_LIBRARY})
SET(JEMALLOC_INCLUDE_DIRS ${JEMALLOC_INCLUDE_DIR})
ENDIF(JEMALLOC_FOUND)
MARK_AS_ADVANCED(
JEMALLOC_INCLUDE_DIR
JEMALLOC_LIBRARY
)
\ No newline at end of file
# - Find Tcmalloc
# Find the native Tcmalloc includes and library
#
# Tcmalloc_INCLUDE_DIR - where to find Tcmalloc.h, etc.
# Tcmalloc_LIBRARIES - List of libraries when using Tcmalloc.
# Tcmalloc_FOUND - True if Tcmalloc found.
if (Tcmalloc_INCLUDE_DIR)
# Already in cache, be silent
set(Tcmalloc_FIND_QUIETLY TRUE)
endif ()
find_path(Tcmalloc_INCLUDE_DIR google/heap-checker.h
/opt/local/include
/usr/local/include
/usr/include
)
set(Tcmalloc_NAMES tcmalloc)
find_library(Tcmalloc_LIBRARY
NAMES ${Tcmalloc_NAMES}
PATHS /usr/lib /usr/local/lib /opt/local/lib
)
if (Tcmalloc_INCLUDE_DIR AND Tcmalloc_LIBRARY)
set(Tcmalloc_FOUND TRUE)
set( Tcmalloc_LIBRARIES ${Tcmalloc_LIBRARY} )
else ()
set(Tcmalloc_FOUND FALSE)
set( Tcmalloc_LIBRARIES )
endif ()
if (Tcmalloc_FOUND)
if (NOT Tcmalloc_FIND_QUIETLY)
message(STATUS "Found Tcmalloc: ${Tcmalloc_LIBRARY}")
endif ()
else ()
message(STATUS "Not Found Tcmalloc: ${Tcmalloc_LIBRARY}")
if (Tcmalloc_FIND_REQUIRED)
message(STATUS "Looked for Tcmalloc libraries named ${TcmallocS_NAMES}.")
message(FATAL_ERROR "Could NOT find Tcmalloc library")
endif ()
endif ()
mark_as_advanced(
Tcmalloc_LIBRARY
Tcmalloc_INCLUDE_DIR
)
# ZeroMQ.cmake from http://lists.gforge.inria.fr/pipermail/simgrid-devel/2012-July/001513.html
SET (ZEROMQ_FIND_QUIETLY TRUE)
SET (ZEROMQ_FIND_REQUIRED FALSE)
IF (NOT ZEROMQ_FOUND)
# Search user environment for headers, then default paths
FIND_PATH (ZEROMQ_INCLUDE_DIR zmq.hpp
PATHS ${ZEROMQROOT}/include $ENV{ZEROMQROOT}/include /usr/local/include
NO_DEFAULT_PATH)
FIND_PATH (ZEROMQ_INCLUDE_DIR zmq.hpp)
GET_FILENAME_COMPONENT (ZEROMQROOT ${ZEROMQ_INCLUDE_DIR} PATH)
# Search user environment for libraries, then default paths
FIND_LIBRARY (ZEROMQ_LIBRARIES zmq
PATHS ${ZEROMQROOT}/lib $ENV{ZEROMQROOT}/lib /usr/local/lib
NO_DEFAULT_PATH)
FIND_LIBRARY (ZEROMQ_LIBRARIES zmq)
# Set ZEROMQ_FOUND and error out if zmq is not found
INCLUDE (FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS (ZEROMQ
DEFAULT_MSG ZEROMQ_LIBRARIES ZEROMQ_INCLUDE_DIR)
IF (ZEROMQ_FOUND)
MESSAGE (STATUS "ZeroMQ found: zmq library and zmq.hpp ")
MESSAGE (STATUS " * includes: ${ZEROMQ_INCLUDE_DIR}")
MESSAGE (STATUS " * libs: ${ZEROMQ_LIBRARIES}")
ELSE (ZEROMQ_FOUND)
IF(${ZEROMQ_LIBRARIES} STREQUAL "ZEROMQ_LIBRARIES-NOTFOUND")
MESSAGE (STATUS "ZeroMQ library does not exist")
ELSEIF(${ZEROMQ_INCLUDE_DIR} STREQUAL "ZEROMQ_INCLUDE_DIR-NOTFOUND")
MESSAGE (STATUS "ZeroMQ has a problem: zmq.hpp does not exist, try to download it from https://github.com/zeromq/cppzmq/blob/master/zmq.hpp")
ENDIF()
ENDIF (ZEROMQ_FOUND)
ENDIF (NOT ZEROMQ_FOUND)
The MIT License (MIT)
Copyright (c) 2014 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.
Simple-Web-Server
=================
A very simple, fast, multithreaded, platform independent HTTP and HTTPS server and client library implemented using C++11 and Boost.Asio. Created to be an easy way to make REST resources available from C++ applications.
See https://github.com/eidheim/Simple-WebSocket-Server for an easy way to make WebSocket/WebSocket Secure endpoints in C++. Also, feel free to check out the new C++ IDE supporting C++11/14/17: https://github.com/cppit/jucipp.
### Features
* Thread pool
* Platform independent
* HTTPS support
* HTTP persistent connection (for HTTP/1.1)
* Client supports chunked transfer encoding
* Timeouts, if any of Server::timeout_request and Server::timeout_content are >0 (default: Server::timeout_request=5 seconds, and Server::timeout_content=300 seconds)
* Simple way to add REST resources using regex for path, and anonymous functions
* Possibility to flush response to clients synchronously (Server::Response::flush).
###Usage
See http_examples.cpp or https_examples.cpp for example usage.
See particularly the JSON-POST (using Boost.PropertyTree) and the GET /match/[number] examples, which are most relevant.
The default_resource includes example use of Server::Response::flush.
### Dependencies
* Boost C++ libraries
* For HTTPS: OpenSSL libraries
### Compile and run
Compile with a C++11 compliant compiler:
```
cmake .
make
```
#### HTTP
Run the server and client examples: `./http_examples`
Direct your favorite browser to for instance http://localhost:8080/
#### HTTPS
Before running the server, an RSA private key (server.key) and an SSL certificate (server.crt) must be created. Follow, for instance, the instructions given here (for a self-signed certificate): http://www.akadia.com/services/ssh_test_certificate.html
Run the server and client examples: `./https_examples`
Direct your favorite browser to for instance https://localhost:8080/
#ifndef CLIENT_HTTP_HPP
#define CLIENT_HTTP_HPP
#include <boost/asio.hpp>
#include <boost/utility/string_ref.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#include <map>
#include <random>
namespace SimpleWeb {
template <class socket_type>
class ClientBase {
public:
virtual ~ClientBase() {}
class Response {
friend class ClientBase<socket_type>;
//Based on http://www.boost.org/doc/libs/1_60_0/doc/html/unordered/hash_equality.html
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;
}
};
public:
std::string http_version, status_code;
std::istream content;
std::unordered_multimap<std::string, std::string, ihash, iequal_to> header;
private:
boost::asio::streambuf content_buffer;
Response(): content(&content_buffer) {}
};
std::shared_ptr<Response> request(const std::string& request_type, const std::string& path="/", boost::string_ref content="",
const std::map<std::string, std::string>& header=std::map<std::string, std::string>()) {
std::string corrected_path=path;
if(corrected_path=="")
corrected_path="/";
boost::asio::streambuf write_buffer;
std::ostream write_stream(&write_buffer);
write_stream << request_type << " " << corrected_path << " HTTP/1.1\r\n";
write_stream << "Host: " << host << "\r\n";
for(auto& h: header) {
write_stream << h.first << ": " << h.second << "\r\n";
}
if(content.size()>0)
write_stream << "Content-Length: " << content.size() << "\r\n";
write_stream << "\r\n";
try {
connect();
boost::asio::write(*socket, write_buffer);
if(content.size()>0)
boost::asio::write(*socket, boost::asio::buffer(content.data(), content.size()));
}
catch(const std::exception& e) {
socket_error=true;
throw std::invalid_argument(e.what());
}
return request_read();
}
std::shared_ptr<Response> request(const std::string& request_type, const std::string& path, std::iostream& content,
const std::map<std::string, std::string>& header=std::map<std::string, std::string>()) {
std::string corrected_path=path;
if(corrected_path=="")
corrected_path="/";
content.seekp(0, std::ios::end);
auto content_length=content.tellp();
content.seekp(0, std::ios::beg);
boost::asio::streambuf write_buffer;
std::ostream write_stream(&write_buffer);
write_stream << request_type << " " << corrected_path << " HTTP/1.1\r\n";
write_stream << "Host: " << host << "\r\n";
for(auto& h: header) {
write_stream << h.first << ": " << h.second << "\r\n";
}
if(content_length>0)
write_stream << "Content-Length: " << content_length << "\r\n";
write_stream << "\r\n";
if(content_length>0)
write_stream << content.rdbuf();
try {
connect();
boost::asio::write(*socket, write_buffer);
}
catch(const std::exception& e) {
socket_error=true;
throw std::invalid_argument(e.what());
}
return request_read();
}
protected:
boost::asio::io_service asio_io_service;
boost::asio::ip::tcp::endpoint asio_endpoint;
boost::asio::ip::tcp::resolver asio_resolver;
std::shared_ptr<socket_type> socket;
bool socket_error;
std::string host;
unsigned short port;
ClientBase(const std::string& host_port, unsigned short default_port) :
asio_resolver(asio_io_service), socket_error(false) {
size_t host_end=host_port.find(':');
if(host_end==std::string::npos) {
host=host_port;
port=default_port;
}
else {
host=host_port.substr(0, host_end);
port=static_cast<unsigned short>(stoul(host_port.substr(host_end+1)));
}
asio_endpoint=boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port);
}
virtual void connect()=0;
void parse_response_header(std::shared_ptr<Response> response, std::istream& stream) const {
std::string line;
getline(stream, line);
size_t version_end=line.find(' ');
if(version_end!=std::string::npos) {
if(5<line.size())
response->http_version=line.substr(5, version_end-5);
if((version_end+1)<line.size())
response->status_code=line.substr(version_end+1, line.size()-(version_end+1)-1);
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())
response->header.insert(std::make_pair(line.substr(0, param_end), line.substr(value_start, line.size()-value_start-1)));
}
getline(stream, line);
}
}
}
std::shared_ptr<Response> request_read() {
std::shared_ptr<Response> response(new Response());
try {
size_t bytes_transferred = boost::asio::read_until(*socket, response->content_buffer, "\r\n\r\n");
size_t num_additional_bytes=response->content_buffer.size()-bytes_transferred;
parse_response_header(response, response->content);
auto header_it=response->header.find("Content-Length");
if(header_it!=response->header.end()) {
auto content_length=stoull(header_it->second);
if(content_length>num_additional_bytes) {
boost::asio::read(*socket, response->content_buffer,
boost::asio::transfer_exactly(content_length-num_additional_bytes));
}
}
else if((header_it=response->header.find("Transfer-Encoding"))!=response->header.end() && header_it->second=="chunked") {
boost::asio::streambuf streambuf;
std::ostream content(&streambuf);
std::streamsize length;
std::string buffer;
do {
size_t bytes_transferred = boost::asio::read_until(*socket, response->content_buffer, "\r\n");
std::string line;
getline(response->content, line);
bytes_transferred-=line.size()+1;
line.pop_back();
length=stol(line, 0, 16);
auto num_additional_bytes=static_cast<std::streamsize>(response->content_buffer.size()-bytes_transferred);
if((2+length)>num_additional_bytes) {
boost::asio::read(*socket, response->content_buffer,
boost::asio::transfer_exactly(2+length-num_additional_bytes));
}
buffer.resize(static_cast<size_t>(length));
response->content.read(&buffer[0], length);
content.write(&buffer[0], length);
//Remove "\r\n"
response->content.get();
response->content.get();
} while(length>0);
std::ostream response_content_output_stream(&response->content_buffer);
response_content_output_stream << content.rdbuf();
}
}
catch(const std::exception& e) {
socket_error=true;
throw std::invalid_argument(e.what());
}
return response;
}
};
template<class socket_type>
class Client : public ClientBase<socket_type> {};
typedef boost::asio::ip::tcp::socket HTTP;
template<>
class Client<HTTP> : public ClientBase<HTTP> {
public:
Client(const std::string& server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {
socket=std::make_shared<HTTP>(asio_io_service);
}
private:
void connect() {
if(socket_error || !socket->is_open()) {
boost::asio::ip::tcp::resolver::query query(host, std::to_string(port));
boost::asio::connect(*socket, asio_resolver.resolve(query));
boost::asio::ip::tcp::no_delay option(true);
socket->set_option(option);
socket_error=false;
}
}
};
}
#endif /* CLIENT_HTTP_HPP */
#ifndef CLIENT_HTTPS_HPP
#define CLIENT_HTTPS_HPP
#include "client_http.hpp"
#include <boost/asio/ssl.hpp>
namespace SimpleWeb {
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;
template<>
class Client<HTTPS> : public ClientBase<HTTPS> {
public:
Client(const std::string& server_port_path, bool verify_certificate=true,
const std::string& cert_file=std::string(), const std::string& private_key_file=std::string(),
const std::string& verify_file=std::string()) :
ClientBase<HTTPS>::ClientBase(server_port_path, 443), asio_context(boost::asio::ssl::context::sslv23) {
if(verify_certificate)
asio_context.set_verify_mode(boost::asio::ssl::verify_peer);
else
asio_context.set_verify_mode(boost::asio::ssl::verify_none);
if(cert_file.size()>0 && private_key_file.size()>0) {
asio_context.use_certificate_chain_file(cert_file);
asio_context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);
}
if(verify_file.size()>0)
asio_context.load_verify_file(verify_file);
socket=std::make_shared<HTTPS>(asio_io_service, asio_context);
}
private:
boost::asio::ssl::context asio_context;
void connect() {
if(socket_error || !socket->lowest_layer().is_open()) {
boost::asio::ip::tcp::resolver::query query(host, std::to_string(port));
boost::asio::connect(socket->lowest_layer(), asio_resolver.resolve(query));
boost::asio::ip::tcp::no_delay option(true);
socket->lowest_layer().set_option(option);
socket->handshake(boost::asio::ssl::stream_base::client);
socket_error=false;
}
}
};
}
#endif /* CLIENT_HTTPS_HPP */
#ifndef SERVER_HTTP_HPP
#define SERVER_HTTP_HPP
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#include <thread>
#include <functional>
#include <iostream>
#include <sstream>
namespace SimpleWeb {
template <class socket_type>
class ServerBase {
public:
virtual ~ServerBase() {}
class Response : public std::ostream {
friend class ServerBase<socket_type>;
private:
boost::asio::yield_context& yield;
boost::asio::streambuf streambuf;
socket_type &socket;
Response(socket_type &socket, boost::asio::yield_context& yield):
std::ostream(&streambuf), yield(yield), socket(socket) {}
public:
size_t size() {
return streambuf.size();
}
void flush() {
boost::system::error_code ec;
boost::asio::async_write(socket, streambuf, yield[ec]);
if(ec)
throw std::runtime_error(ec.message());
}
};
class Content : public std::istream {
friend class ServerBase<socket_type>;
public:
size_t size() {
return streambuf.size();
}
std::string string() {