Support system-assigned port via two part listen()

This fixes #46 by allowing the user to separate the port bind from the
blocking listen(). Two new API functions bind_to_any_port() (which
returns the system-assigned port) and listen_after_bind() are equivalent
to the existing listen().
This commit is contained in:
Scott Graham 2018-04-15 00:56:00 -07:00
parent 0e239a0014
commit 0515c6aad6
2 changed files with 82 additions and 40 deletions

115
httplib.h
View file

@ -196,6 +196,9 @@ public:
void set_error_handler(Handler handler);
void set_logger(Logger logger);
int bind_to_any_port(const char* host, int socket_flags = 0);
bool listen_after_bind();
bool listen(const char* host, int port, int socket_flags = 0);
bool is_running() const;
@ -210,6 +213,8 @@ private:
typedef std::vector<std::pair<std::regex, Handler>> Handlers;
socket_t create_server_socket(const char* host, int port, int socket_flags) const;
int bind_internal(const char* host, int port, int socket_flags);
bool listen_internal();
bool routing(Request& req, Response& res);
bool handle_file_request(Request& req, Response& res);
@ -1399,49 +1404,20 @@ inline void Server::set_logger(Logger logger)
logger_ = logger;
}
inline int Server::bind_to_any_port(const char* host, int socket_flags)
{
return bind_internal(host, 0, socket_flags);
}
inline bool Server::listen_after_bind() {
return listen_internal();
}
inline bool Server::listen(const char* host, int port, int socket_flags)
{
if (!is_valid()) {
if (bind_internal(host, port, socket_flags) < 0)
return false;
}
svr_sock_ = create_server_socket(host, port, socket_flags);
if (svr_sock_ == -1) {
return false;
}
auto ret = true;
for (;;) {
auto val = detail::select_read(svr_sock_, 0, 100000);
if (val == 0) { // Timeout
if (svr_sock_ == -1) {
// The server socket was closed by 'stop' method.
break;
}
continue;
}
socket_t sock = accept(svr_sock_, NULL, NULL);
if (sock == -1) {
if (svr_sock_ != -1) {
detail::close_socket(svr_sock_);
ret = false;
} else {
; // The server socket was closed by user.
}
break;
}
// TODO: Use thread pool...
std::thread([=]() {
read_and_close_socket(sock);
}).detach();
}
return ret;
return listen_internal();
}
inline bool Server::is_running() const
@ -1560,6 +1536,65 @@ inline socket_t Server::create_server_socket(const char* host, int port, int soc
}, socket_flags);
}
inline int Server::bind_internal(const char* host, int port, int socket_flags)
{
if (!is_valid()) {
return -1;
}
svr_sock_ = create_server_socket(host, port, socket_flags);
if (svr_sock_ == -1) {
return -1;
}
if (port == 0) {
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&sin), &len) == -1) {
return -1;
}
return ntohs(sin.sin_port);
} else {
return port;
}
}
inline bool Server::listen_internal()
{
auto ret = true;
for (;;) {
auto val = detail::select_read(svr_sock_, 0, 100000);
if (val == 0) { // Timeout
if (svr_sock_ == -1) {
// The server socket was closed by 'stop' method.
break;
}
continue;
}
socket_t sock = accept(svr_sock_, NULL, NULL);
if (sock == -1) {
if (svr_sock_ != -1) {
detail::close_socket(svr_sock_);
ret = false;
} else {
; // The server socket was closed by user.
}
break;
}
// TODO: Use thread pool...
std::thread([=]() {
read_and_close_socket(sock);
}).detach();
}
return ret;
}
inline bool Server::routing(Request& req, Response& res)
{
if (req.method == "GET" && handle_file_request(req, res)) {

View file

@ -240,6 +240,13 @@ TEST(ConnectionErrorTest, Timeout)
ASSERT_TRUE(res == nullptr);
}
TEST(Server, BindAndListenSeparately) {
Server svr(httplib::HttpVersion::v1_1);
int port = svr.bind_to_any_port("localhost");
ASSERT_TRUE(port > 0);
svr.stop();
}
class ServerTest : public ::testing::Test {
protected:
ServerTest()