mirror of
https://github.com/boostorg/beast.git
synced 2025-07-30 04:47:29 +02:00
Add http-server example
This commit is contained in:
@ -9,6 +9,7 @@ Version 66:
|
||||
* Tidy up message piecewise ctors
|
||||
* Add header aliases
|
||||
* basic_fields optimizations
|
||||
* Add http-server example
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
||||
[def __asio_handler_allocate__ [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]]
|
||||
[def __io_service__ [@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `io_service`]]
|
||||
[def __socket__ [@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`]]
|
||||
[def __ssl_stream__ [@http://www.boost.org/doc/html/boost_asio/reference/ssl_stream.html `boost::asio::ssl::stream`]]
|
||||
[def __ssl_stream__ [@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream`]]
|
||||
[def __streambuf__ [@http://www.boost.org/doc/html/boost_asio/reference/streambuf.html `boost::asio::streambuf`]]
|
||||
[def __use_future__ [@http://www.boost.org/doc/html/boost_asio/reference/use_future_t.html `boost::asio::use_future`]]
|
||||
[def __void_or_deduced__ [@http://www.boost.org/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]]
|
||||
|
@ -66,6 +66,18 @@ over a TLS connection. Requires OpenSSL to build.
|
||||
|
||||
|
||||
|
||||
[section HTTP Server]
|
||||
|
||||
This example implements a very simple HTTP server with
|
||||
some optimizations suitable for calculating benchmarks.
|
||||
|
||||
* [repo_file example/http-server/fields_alloc.cpp]
|
||||
* [repo_file example/http-server/http_server.cpp]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[section WebSocket Client (with SSL)]
|
||||
|
||||
Establish a WebSocket connection over an encrypted TLS connection,
|
||||
|
@ -3,6 +3,7 @@
|
||||
add_subdirectory (echo-op)
|
||||
add_subdirectory (http-client)
|
||||
add_subdirectory (http-crawl)
|
||||
add_subdirectory (http-server)
|
||||
add_subdirectory (server-framework)
|
||||
add_subdirectory (websocket-client)
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
build-project echo-op ;
|
||||
build-project http-client ;
|
||||
build-project http-crawl ;
|
||||
build-project http-server ;
|
||||
build-project server-framework ;
|
||||
build-project websocket-client ;
|
||||
|
||||
|
17
example/http-server/CMakeLists.txt
Normal file
17
example/http-server/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
# Part of Beast
|
||||
|
||||
GroupSources(include/beast beast)
|
||||
|
||||
GroupSources(example/http-server "/")
|
||||
|
||||
add_executable (http-server
|
||||
${BEAST_INCLUDES}
|
||||
fields_alloc.hpp
|
||||
http_server.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(http-server
|
||||
Beast
|
||||
${Boost_FILESYSTEM_LIBRARY}
|
||||
)
|
||||
|
13
example/http-server/Jamfile
Normal file
13
example/http-server/Jamfile
Normal file
@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe http-server :
|
||||
http_server.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
195
example/http-server/fields_alloc.hpp
Normal file
195
example/http-server/fields_alloc.hpp
Normal file
@ -0,0 +1,195 @@
|
||||
//
|
||||
// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_EXAMPLE_FIELDS_ALLOC_HPP
|
||||
#define BEAST_EXAMPLE_FIELDS_ALLOC_HPP
|
||||
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct static_pool
|
||||
{
|
||||
std::size_t size_;
|
||||
std::size_t refs_ = 1;
|
||||
std::size_t count_ = 0;
|
||||
char* p_;
|
||||
|
||||
char*
|
||||
end()
|
||||
{
|
||||
return reinterpret_cast<char*>(this+1) + size_;
|
||||
}
|
||||
|
||||
explicit
|
||||
static_pool(std::size_t size)
|
||||
: size_(size)
|
||||
, p_(reinterpret_cast<char*>(this+1))
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
static
|
||||
static_pool&
|
||||
construct(std::size_t size)
|
||||
{
|
||||
auto p = new char[sizeof(static_pool) + size];
|
||||
return *(new(p) static_pool{size});
|
||||
}
|
||||
|
||||
static_pool&
|
||||
share()
|
||||
{
|
||||
++refs_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
destroy()
|
||||
{
|
||||
if(refs_--)
|
||||
return;
|
||||
this->~static_pool();
|
||||
delete[] reinterpret_cast<char*>(this);
|
||||
}
|
||||
|
||||
void*
|
||||
alloc(std::size_t n)
|
||||
{
|
||||
auto last = p_ + n;
|
||||
if(last >= end())
|
||||
BOOST_THROW_EXCEPTION(std::bad_alloc{});
|
||||
++count_;
|
||||
auto p = p_;
|
||||
p_ = last;
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
dealloc()
|
||||
{
|
||||
if(--count_)
|
||||
return;
|
||||
p_ = reinterpret_cast<char*>(this+1);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** A non-thread-safe allocator optimized for @ref basic_fields.
|
||||
|
||||
This allocator obtains memory from a pre-allocated memory block
|
||||
of a given size. It does nothing in deallocate until all
|
||||
previously allocated blocks are deallocated, upon which it
|
||||
resets the internal memory block for re-use.
|
||||
|
||||
To use this allocator declare an instance persistent to the
|
||||
connection or session, and construct with the block size.
|
||||
A good rule of thumb is 20% more than the maximum allowed
|
||||
header size. For example if the application only allows up
|
||||
to an 8,000 byte header, the block size could be 9,600.
|
||||
|
||||
Then, for every instance of `message` construct the header
|
||||
with a copy of the previously declared allocator instance.
|
||||
*/
|
||||
template<class T>
|
||||
struct fields_alloc
|
||||
{
|
||||
detail::static_pool& pool_;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
using is_always_equal = std::false_type;
|
||||
using pointer = T*;
|
||||
using reference = T&;
|
||||
using const_pointer = T const*;
|
||||
using const_reference = T const&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
template<class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = fields_alloc<U>;
|
||||
};
|
||||
|
||||
explicit
|
||||
fields_alloc(std::size_t size)
|
||||
: pool_(detail::static_pool::construct(size))
|
||||
{
|
||||
}
|
||||
|
||||
fields_alloc(fields_alloc const& other)
|
||||
: pool_(other.pool_.share())
|
||||
{
|
||||
}
|
||||
|
||||
template<class U>
|
||||
fields_alloc(fields_alloc<U> const& other)
|
||||
: pool_(other.pool_.share())
|
||||
{
|
||||
}
|
||||
|
||||
~fields_alloc()
|
||||
{
|
||||
pool_.destroy();
|
||||
}
|
||||
|
||||
value_type*
|
||||
allocate(size_type n)
|
||||
{
|
||||
return static_cast<value_type*>(
|
||||
pool_.alloc(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void
|
||||
deallocate(value_type*, size_type)
|
||||
{
|
||||
pool_.dealloc();
|
||||
}
|
||||
|
||||
#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000
|
||||
template<class U, class... Args>
|
||||
void
|
||||
construct(U* ptr, Args&&... args)
|
||||
{
|
||||
::new((void*)ptr) U(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
void
|
||||
destroy(U* ptr)
|
||||
{
|
||||
ptr->~U();
|
||||
}
|
||||
#endif
|
||||
|
||||
template<class U>
|
||||
friend
|
||||
bool
|
||||
operator==(
|
||||
fields_alloc const& lhs,
|
||||
fields_alloc<U> const& rhs)
|
||||
{
|
||||
return &lhs.pool_ == &rhs.pool_;
|
||||
}
|
||||
|
||||
template<class U>
|
||||
friend
|
||||
bool
|
||||
operator!=(
|
||||
fields_alloc const& lhs,
|
||||
fields_alloc<U> const& rhs)
|
||||
{
|
||||
return ! (lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
314
example/http-server/http_server.cpp
Normal file
314
example/http-server/http_server.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
//
|
||||
// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "fields_alloc.hpp"
|
||||
|
||||
#include <beast/core.hpp>
|
||||
#include <beast/http.hpp>
|
||||
#include <beast/version.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace ip = boost::asio::ip; // from <boost/asio.hpp>
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
|
||||
namespace http = beast::http; // from <beast/http.hpp>
|
||||
|
||||
// Return a reasonable mime type based on the extension of a file.
|
||||
//
|
||||
beast::string_view
|
||||
mime_type(boost::filesystem::path const& path)
|
||||
{
|
||||
using beast::iequals;
|
||||
auto const ext = path.extension().string();
|
||||
if(iequals(ext, ".txt")) return "text/plain";
|
||||
if(iequals(ext, ".htm")) return "text/html";
|
||||
if(iequals(ext, ".html")) return "text/html";
|
||||
if(iequals(ext, ".php")) return "text/html";
|
||||
if(iequals(ext, ".css")) return "text/css";
|
||||
if(iequals(ext, ".js")) return "application/javascript";
|
||||
if(iequals(ext, ".json")) return "application/json";
|
||||
if(iequals(ext, ".xml")) return "application/xml";
|
||||
if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
|
||||
if(iequals(ext, ".flv")) return "video/x-flv";
|
||||
if(iequals(ext, ".png")) return "image/png";
|
||||
if(iequals(ext, ".jpe")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpeg")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpg")) return "image/jpeg";
|
||||
if(iequals(ext, ".gif")) return "image/gif";
|
||||
if(iequals(ext, ".bmp")) return "image/bmp";
|
||||
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
|
||||
if(iequals(ext, ".tiff")) return "image/tiff";
|
||||
if(iequals(ext, ".tif")) return "image/tiff";
|
||||
if(iequals(ext, ".svg")) return "image/svg+xml";
|
||||
if(iequals(ext, ".svgz")) return "image/svg+xml";
|
||||
return "application/text";
|
||||
}
|
||||
|
||||
class http_worker
|
||||
{
|
||||
public:
|
||||
http_worker(http_worker const&) = delete;
|
||||
http_worker& operator=(http_worker const&) = delete;
|
||||
|
||||
http_worker(tcp::acceptor& acceptor, const std::string& doc_root) :
|
||||
acceptor_(acceptor),
|
||||
doc_root_(doc_root),
|
||||
socket_(acceptor.get_io_service()),
|
||||
alloc_(8192),
|
||||
request_deadline_(acceptor.get_io_service(),
|
||||
std::chrono::steady_clock::time_point::max())
|
||||
{
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
accept();
|
||||
check_deadline();
|
||||
}
|
||||
|
||||
private:
|
||||
using request_body_t = http::basic_dynamic_body<beast::static_buffer_n<1024 * 1024>>;
|
||||
|
||||
// The acceptor used to listen for incoming connections.
|
||||
tcp::acceptor& acceptor_;
|
||||
|
||||
// The path to the root of the document directory.
|
||||
std::string doc_root_;
|
||||
|
||||
// The socket for the currently connected client.
|
||||
tcp::socket socket_;
|
||||
|
||||
// The buffer for performing reads
|
||||
beast::static_buffer_n<8192> buffer_;
|
||||
|
||||
// The parser for reading the requests
|
||||
using alloc_type = fields_alloc<char>;
|
||||
alloc_type alloc_;
|
||||
boost::optional<http::request_parser<request_body_t, alloc_type>> parser_;
|
||||
|
||||
// The timer putting a time limit on requests.
|
||||
boost::asio::basic_waitable_timer<std::chrono::steady_clock> request_deadline_;
|
||||
|
||||
// The response message.
|
||||
http::response<http::string_body> response_;
|
||||
|
||||
// The response serializer.
|
||||
boost::optional<http::response_serializer<http::string_body>> serializer_;
|
||||
|
||||
void accept()
|
||||
{
|
||||
// Clean up any previous connection.
|
||||
beast::error_code ec;
|
||||
socket_.close(ec);
|
||||
buffer_.consume(buffer_.size());
|
||||
|
||||
acceptor_.async_accept(
|
||||
socket_,
|
||||
[this](beast::error_code ec)
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Request must be fully processed within 60 seconds.
|
||||
request_deadline_.expires_from_now(
|
||||
std::chrono::seconds(60));
|
||||
|
||||
read_header();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void read_header()
|
||||
{
|
||||
// On each read the parser needs to be destroyed and
|
||||
// recreated. We store it in a boost::optional to
|
||||
// achieve that.
|
||||
//
|
||||
// Arguments passed to the parser constructor are
|
||||
// forwarded to the message object. A single argument
|
||||
// is forwarded to the body constructor.
|
||||
//
|
||||
// We construct the dynamic body with a 1MB limit
|
||||
// to prevent vulnerability to buffer attacks.
|
||||
//
|
||||
parser_.emplace(
|
||||
std::piecewise_construct,
|
||||
std::make_tuple(),
|
||||
std::make_tuple(alloc_));
|
||||
|
||||
http::async_read_header(
|
||||
socket_,
|
||||
buffer_,
|
||||
*parser_,
|
||||
[this](beast::error_code ec)
|
||||
{
|
||||
if (ec)
|
||||
accept();
|
||||
else
|
||||
read_body();
|
||||
});
|
||||
}
|
||||
|
||||
void read_body()
|
||||
{
|
||||
http::async_read(
|
||||
socket_,
|
||||
buffer_,
|
||||
*parser_,
|
||||
[this](beast::error_code ec)
|
||||
{
|
||||
if (ec)
|
||||
accept();
|
||||
else
|
||||
process_request(parser_->get());
|
||||
});
|
||||
}
|
||||
|
||||
void process_request(http::request<request_body_t, http::basic_fields<alloc_type>> const& req)
|
||||
{
|
||||
response_.version = 11;
|
||||
response_.set(http::field::connection, "close");
|
||||
|
||||
switch (req.method())
|
||||
{
|
||||
case http::verb::get:
|
||||
response_.result(http::status::ok);
|
||||
response_.set(http::field::server, "Beast");
|
||||
load_file(req.target());
|
||||
break;
|
||||
|
||||
default:
|
||||
// We return responses indicating an error if
|
||||
// we do not recognize the request method.
|
||||
response_.result(http::status::bad_request);
|
||||
response_.set(http::field::content_type, "text/plain");
|
||||
response_.body = "Invalid request-method '" + req.method_string().to_string() + "'";
|
||||
response_.prepare_payload();
|
||||
break;
|
||||
}
|
||||
|
||||
write_response();
|
||||
}
|
||||
|
||||
void load_file(beast::string_view target)
|
||||
{
|
||||
// Request path must be absolute and not contain "..".
|
||||
if (target.empty() || target[0] != '/' || target.find("..") != std::string::npos)
|
||||
{
|
||||
response_.result(http::status::not_found);
|
||||
response_.set(http::field::content_type, "text/plain");
|
||||
response_.body = "File not found\r\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string full_path = doc_root_;
|
||||
full_path.append(target.data(), target.size());
|
||||
|
||||
// Open the file to send back.
|
||||
std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
|
||||
if (!is)
|
||||
{
|
||||
response_.result(http::status::not_found);
|
||||
response_.set(http::field::content_type, "text/plain");
|
||||
response_.body = "File not found\r\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill out the reply to be sent to the client.
|
||||
response_.set(http::field::content_type, mime_type(target.to_string()));
|
||||
response_.body.clear();
|
||||
for (char buf[512]; is.read(buf, sizeof(buf)).gcount() > 0;)
|
||||
response_.body.append(buf, static_cast<std::size_t>(is.gcount()));
|
||||
response_.prepare_payload();
|
||||
}
|
||||
|
||||
void write_response()
|
||||
{
|
||||
response_.set(http::field::content_length, response_.body.size());
|
||||
|
||||
serializer_.emplace(response_);
|
||||
|
||||
http::async_write(
|
||||
socket_,
|
||||
*serializer_,
|
||||
[this](beast::error_code ec)
|
||||
{
|
||||
socket_.shutdown(tcp::socket::shutdown_send, ec);
|
||||
accept();
|
||||
});
|
||||
}
|
||||
|
||||
void check_deadline()
|
||||
{
|
||||
// The deadline may have moved, so check it has really passed.
|
||||
if (request_deadline_.expires_at() <= std::chrono::steady_clock::now())
|
||||
{
|
||||
// Close socket to cancel any outstanding operation.
|
||||
beast::error_code ec;
|
||||
socket_.close();
|
||||
|
||||
// Sleep indefinitely until we're given a new deadline.
|
||||
request_deadline_.expires_at(
|
||||
std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
request_deadline_.async_wait(
|
||||
[this](beast::error_code)
|
||||
{
|
||||
check_deadline();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check command line arguments.
|
||||
if (argc != 5)
|
||||
{
|
||||
std::cerr << "Usage: http_server <address> <port> <doc_root> <num_workers>\n";
|
||||
std::cerr << " For IPv4, try:\n";
|
||||
std::cerr << " receiver 0.0.0.0 80 . 100\n";
|
||||
std::cerr << " For IPv6, try:\n";
|
||||
std::cerr << " receiver 0::0 80 . 100\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
auto address = ip::address::from_string(argv[1]);
|
||||
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
|
||||
std::string doc_root = argv[3];
|
||||
int num_workers = std::atoi(argv[4]);
|
||||
|
||||
boost::asio::io_service ios{1};
|
||||
tcp::acceptor acceptor{ios, {address, port}};
|
||||
|
||||
std::list<http_worker> workers;
|
||||
for (int i = 0; i < num_workers; ++i)
|
||||
{
|
||||
workers.emplace_back(acceptor, doc_root);
|
||||
workers.back().start();
|
||||
}
|
||||
|
||||
ios.run();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "Exception: " << e.what() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user