User Tools

Site Tools


SSL variant of pure C++ web server

server.hpp
/*
 *
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 *
 */
 
#ifndef SERVER_HPP
#define SERVER_HPP
 
#include <sys/socket.h>
#include <unistd.h>
 
#include <openssl/ssl.h>
#include <openssl/err.h>
 
#include <thread>
#include <mutex>
#include <memory>
 
namespace srv5 {
 
class server {
    private:
        struct sockaddr_storage addr;
        int socket;
        int backlog;
 
        std::queue<int> queue;
        std::condition_variable cv;
        std::mutex mutex;
        std::vector<std::shared_ptr<std::thread>> pool;
        int threads;
 
        SSL_CTX *context;
 
        std::string timestamp();
        void push(int socket);
 
    public:
        server(int port, int backlog, int threads);
        ~server();
        void team();
        void run();
        void stop();
};
 
} // namespace srv5
 
#endif
server.cpp
/*
 *
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 *
 */
 
#include <sys/types.h>
#include <sys/event.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
 
#include <openssl/ssl.h>
#include <openssl/err.h>
 
#include <iostream>
#include <fstream>
#include <sstream>
#include <functional>
#include <condition_variable>
#include <queue>
#include <thread>
#include <mutex>
#include <chrono>
#include <memory>
#include <stdexcept>
#include <iomanip>
#include <regex>
 
 
#include "server.hpp"
 
namespace utils {
 
std::vector<std::string> split(const std::string& source, const std::string& delimiter) {
    std::vector<std::string> substringv;
    std::size_t begin = 0;
    std::size_t position = std::string::npos;
    while ((position = source.find(delimiter, begin)) != std::string::npos) {
        std::string substring = source.substr(begin, position - begin);
        begin = position + delimiter.size();
        substringv.push_back(substring);
    }
    std::string substring = source.substr(begin);
    substringv.push_back(substring);
    return substringv;
}
 
std::string timestamp() {
    std::stringstream ss;
    std::time_t now = std::time(0);
    ss << std::put_time(std::gmtime(&now), "%a, %d %b %Y %T %Z");
    return ss.str();
}
 
}
 
namespace srv5 {
 
 
server::server(int port, int backlog, int threads) : backlog(backlog), threads(threads) {
 
    ::SSL_load_error_strings();
    ::OpenSSL_add_ssl_algorithms();
 
    const SSL_METHOD *method = SSLv23_server_method();
    context = ::SSL_CTX_new(method);
    if (!context) {
        throw std::runtime_error("unable to create SSL context");
    }
    ::SSL_CTX_set_ecdh_auto(context, 1);
    if (::SSL_CTX_use_certificate_file(context, "server.crt", SSL_FILETYPE_PEM) <= 0) {
        throw std::runtime_error("unable to read certificate file");
    }
    if (::SSL_CTX_use_PrivateKey_file(context, "server.key", SSL_FILETYPE_PEM) <= 0 ) {
        throw std::runtime_error("unable to read key file");
    }
 
 
    if((socket = ::socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        throw std::runtime_error("cannot socket()");
    }
 
    int optval = 1;
    if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
        throw std::runtime_error("cannot set socket opttion");
    }
 
    auto paddr = reinterpret_cast<struct sockaddr_in*>(&addr);
    paddr->sin_family = AF_INET;
    paddr->sin_addr.s_addr = INADDR_ANY;
    paddr->sin_port = htons(port);
    paddr->sin_len = sizeof(struct sockaddr_in);
 
    if (::bind(socket, reinterpret_cast<struct sockaddr*>(paddr), paddr->sin_len) < 0) {
        throw std::runtime_error("cannot bind socket");
    }
 
}
 
void server::run() {
    if (::listen(socket, backlog) < 0) {
        throw std::runtime_error("cannot listen socket");
    }
    int newsocket;
    while ((newsocket = ::accept(socket, NULL, 0)) > 0) {
        push(std::move(newsocket));
    }
}
 
void server::push(int socket) {
    std::unique_lock<std::mutex> lock(mutex);
    queue.push(socket);
    cv.notify_all();
}
 
void server::team() {
    auto worker = [&]{
        while(true) {
            std::unique_lock<std::mutex> lock(mutex);
            cv.wait(lock, [&]{ return !queue.empty(); });
            while(!queue.empty()) {
                auto socket = queue.front();
                queue.pop();
                cv.notify_one();
 
                SSL *ssl = ::SSL_new(context);
                ::SSL_set_fd(ssl, socket);
 
                if (::SSL_accept(ssl) > 0) {
                    char buffer[512];
                    std::string in;
 
                    while (in.find("\r\n\r\n", 0) == std::string::npos) {
                        int size = ::SSL_read(ssl, buffer, 512);
                        in.append(buffer, size);
                    }
 
                    auto headerv = utils::split(in, "\r\n");
                    auto line = headerv.front();
 
                    std::smatch match;
                    std::regex_search(line, match, std::regex("^(GET)[ ]+(.*)[ ]+(HTTP/1.[01])$"));
                    if (match.size() == 4) {
                        auto resourse = utils::split(match[2], "?").at(0);
 
                        if (resourse == "/") resourse = "/index.html";
 
                        std::string body;
                        std::size_t size = 0;
                        auto path = "./public/" + resourse;
                        std::ifstream is(path, std::ios::binary | std::ios::in);
 
                        if (is) {
                            char buffer[1024];
                            while (is.read(buffer, sizeof(buffer)).gcount() > 0) {
                                body.append(buffer, is.gcount());
                                size += is.gcount();
                            }
                        }
 
                        std::string mime = "application/octet-stream";
                        auto ext = path.substr(path.find_last_of(".") + 1);
                        if (ext == path) ext = "";
                        if (ext == "html") { mime = "text/html"; }
                        else if (ext == "css") { mime = "text/css"; }
                        else if (ext == "js") { mime = "application/javascript"; }
                        else if (ext == "png") { mime = "image/png"; }
                        else if (ext == "jpg") { mime = "image/jpeg"; }
                        else if (ext == "ico") { mime = "image/x-icon"; };
 
                        std::stringstream head;
                        if (size > 0) {
                            head << "HTTP/1.1 200 OK\r\n";
                            head << "Content-Length: " << size << "\r\n";
                            head << "Content-Type: " << mime << "\r\n";
                            head << "Connection: close\r\n";
                        } else {
                            head << "HTTP/1.1 404 Not found\r\n";
                            head << "Content-Type: text/plain\r\n";
                            head << "Connection: close\r\n";
                        }
                        head << "Server: Srv5/0.1\r\n";
                        head << "Date: " << utils::timestamp() << "\r\n";
                        head << "\r\n";
 
                        std::string res;
                        res.append(head.str());
                        res.append(body);
                        ::SSL_write(ssl, res.c_str(), res.length());
                    }
                }
                ::SSL_free(ssl);
                ::shutdown(socket, SHUT_RDWR);
                ::close(socket);
            }
        }
    };
 
    for (std::size_t i = 0; i < threads; ++i) {
        auto t = new std::thread(std::move(worker));
        std::shared_ptr<std::thread> thread(std::move(t));
        pool.push_back(thread);
    }
    for (std::size_t i = 0; i < pool.size(); ++i) {
        pool[i]->detach();
    }
}
 
 
server::~server() {
    ::close(socket);
}
 
void server::stop() {
    std::cerr << "# stop server and exit" << std::endl;
    ::shutdown(socket, SHUT_RDWR);
    ::close(socket);
    ::exit(0);
}
 
} // namespace srv5

Run

$ ab -c 2000 -n10000 'https://localhost:1026/'
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:        Srv5/0.1
Server Hostname:        localhost
Server Port:            1026
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256
TLS Server Name:        localhost

Document Path:          /
Document Length:        22177 bytes

Concurrency Level:      2000
Time taken for tests:   22.019 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      223180000 bytes
HTML transferred:       221770000 bytes
Requests per second:    454.15 [#/sec] (mean)
Time per request:       4403.864 [ms] (mean)
Time per request:       2.202 [ms] (mean, across all concurrent requests)
Transfer rate:          9898.09 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        7 3991 873.2   3813    6446
Processing:     0    0   3.1      0     308
Waiting:        0    0   0.1      0       2
Total:        309 3991 873.1   3813    6447

Percentage of the requests served within a certain time (ms)
  50%   3813
  66%   4005
  75%   4125
  80%   4416
  90%   5177
  95%   5702
  98%   6076
  99%   6247
 100%   6447 (longest request)
$

First PagePrevious PageBack to overviewNext PageLast Page