User Tools

Site Tools


Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cpp:ssl-v [2019-05-28 15:26]
cpp:ssl-v [2020-02-15 00:57] (current)
Line 1: Line 1:
 +
 +=====SSL variant of pure C++ web server=====
 +
 +<code c++ 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
 +</​code>​
 +
 +
 +
 +<code c++ 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
 +</​code>​
 +
 +===Run===
 +
 +<​file>​
 +$ 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)
 +$
 +</​file>​
 +
 +----
 +[<>]