User Tools

Site Tools


Differences

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

Link to this comparison view

cpp:ssl-srv7 [2019-05-28 15:26]
cpp:ssl-srv7 [2020-02-15 00:57] (current)
Line 1: Line 1:
  
 +=====Asio SSL multithread server=====
 +
 +===main.cpp===
 +
 +<code c++ main.cpp>​
 +/*
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + */
 +
 +#include <​iostream>​
 +#include <​string>​
 +#include <​asio.hpp>​
 +
 +#include "​server.hpp"​
 +
 +int main(int argc, char* argv[]) {
 +    try {
 +        srv7::​server server("​0.0.0.0",​ "​1026",​ 5);
 +        server.run();​
 +    } catch (std::​exception&​ e) {
 +        std::cerr << "​exception:​ " << e.what() << "​\n";​
 +    }
 +    return 0;
 +}
 +
 +</​code>​
 +
 +
 +===server.hpp===
 +
 +<code c++ server.hpp>​
 +/*
 + *
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + *
 + */
 +
 +#ifndef SRV7_SERVER_HPP
 +#define SRV7_SERVER_HPP
 +
 +#include <​string>​
 +#include <​vector>​
 +
 +#include <​asio.hpp>​
 +#include <​asio/​ssl.hpp>​
 +
 +#include "​connection.hpp"​
 +
 +namespace srv7 {
 +
 +class server {
 +  private:
 +    std::size_t thread_pool_size;​
 +    asio::​io_context io_context;
 +    asio::​signal_set signals;
 +    asio::​ip::​tcp::​acceptor acceptor;
 +    asio::​ssl::​context ssl_context;​
 +
 +    std::​shared_ptr<​connection>​ connection;
 +
 +    void accept();
 +    void stop();
 +
 +  public:
 +    explicit server(
 +        const std::​string&​ address,
 +        const std::​string&​ port,
 +        std::size_t thread_pool_size
 +    );
 +
 +    void run();
 +
 +    server(const server&​) = delete;
 +    server& operator=(const server&​) = delete;
 +
 +};
 +
 +} // namespace srv7
 +
 +#endif // SRV7_SERVER_HPP
 +
 +</​code>​
 +
 +===server.cpp===
 +
 +<code c++ server.cpp>​
 +/*
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + */
 +
 +#include <​iostream>​
 +#include <​vector>​
 +#include <​memory>​
 +
 +#include <​asio.hpp>​
 +#include <​asio/​ssl.hpp>​
 +
 +#include "​server.hpp"​
 +
 +namespace srv7 {
 +
 +server::​server(const std::​string&​ address, const std::​string&​ port, std::size_t thread_pool_size)
 +    : thread_pool_size(thread_pool_size),​
 +      signals(io_context),​
 +      acceptor(io_context),​
 +      ssl_context(asio::​ssl::​context::​sslv23),​
 +      connection()
 +    {
 +
 +    signals.add(SIGINT);​
 +    signals.add(SIGTERM);​
 +
 +    auto signal_handler = [this](std::​error_code ec, int signo) {
 +        stop();
 +    };
 +
 +    signals.async_wait(signal_handler);​
 +
 +    ssl_context.set_options(
 +        asio::​ssl::​context::​default_workarounds
 +        | asio::​ssl::​context::​no_sslv2
 +        | asio::​ssl::​context::​single_dh_use
 +    );
 +    ssl_context.use_certificate_chain_file("​server.pem"​);​
 +    ssl_context.use_private_key_file("​server.pem",​ asio::​ssl::​context::​pem);​
 +
 +    asio::​ip::​tcp::​resolver resolver(io_context);​
 +    asio::​ip::​tcp::​endpoint endpoint = *resolver.resolve(address,​ port).begin();​
 +
 +    acceptor.open(endpoint.protocol());​
 +    acceptor.set_option(asio::​ip::​tcp::​acceptor::​reuse_address(true));​
 +    acceptor.bind(endpoint);​
 +    acceptor.listen(2048);​
 +
 +    this->​accept();​
 +}
 +
 +void server::​run() {
 +    std::​vector<​std::​shared_ptr<​asio::​thread>>​ threads;
 +    for (std::​size_t i = 0; i < thread_pool_size;​ ++i) {
 +
 +        auto f = [this]{ io_context.run();​ };
 +        auto t = new asio::​thread(std::​move(f));​
 +
 +        std::​shared_ptr<​asio::​thread>​ thread(std::​move(t));​
 +        threads.push_back(thread);​
 +    }
 +    for (std::​size_t i = 0; i < threads.size();​ ++i) {
 +        threads[i]->​join();​
 +    }
 +}
 +
 +void server::​accept() {
 +    connection.reset(new class connection(ssl_context,​ io_context));​
 +    auto handler = [this](const asio::​error_code&​ ec) {
 +        if (!ec) {
 +            connection->​start();​
 +        }
 +        this->​accept();​
 +    };
 +    acceptor.async_accept(connection->​get_socket(),​ handler);
 +}
 +
 +void server::​stop() {
 +    io_context.stop();​
 +}
 +
 +} // namespace srv7
 +
 +</​code>​
 +
 +
 +===connection.hpp===
 +
 +<code c++ connection.hpp>​
 +/*
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + */
 +
 +
 +#ifndef SRV7_CONNECTION_HPP
 +#define SRV7_CONNECTION_HPP
 +
 +#include <​memory>​
 +#include <​array>​
 +
 +#include <​asio.hpp>​
 +#include <​asio/​ssl.hpp>​
 +
 +namespace srv7 {
 +
 +class connection : public std::​enable_shared_from_this<​connection>​ {
 +  private:
 +    asio::​io_context::​strand strand;
 +    asio::​ip::​tcp::​socket socket;
 +    asio::​ssl::​context&​ ssl_context;​
 +
 +    asio::​ssl::​stream<​asio::​ip::​tcp::​socket>​ *ssl_socket;​
 +
 +    std::string response;
 +    std::string buffer;
 +
 +    void handshake();​
 +    void read();
 +    void write();
 +  public:
 +    explicit connection(
 +        asio::​ssl::​context&​ ssl_context,​
 +        asio::​io_context&​ io_context
 +    );
 +    connection(const connection&​) = delete;
 +    connection&​ operator=(const connection&​) = delete;
 +
 +    asio::​ip::​tcp::​socket&​ get_socket();​
 +
 +    void start();
 +    void stop();
 +    ~connection();​
 +};
 +
 +} // namespace srv7
 +
 +#endif // SRV7_CONNECTION_HPP
 +
 +</​code>​
 +
 +===connection.cpp===
 +
 +<code c++ connection.cpp>​
 +/*
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + */
 +
 +#include <​vector>​
 +#include <​sstream>​
 +#include <​filesystem>​
 +#include <​utility>​
 +#include <​iomanip>​
 +#include <​chrono>​
 +
 +#include <​asio.hpp>​
 +#include <​asio/​ssl.hpp>​
 +
 +#include "​connection.hpp"​
 +
 +namespace srv7 {
 +
 +connection::​connection(asio::​ssl::​context&​ ssl_context,​ asio::​io_context&​ io_context)
 +    : strand(io_context),​ socket(io_context),​ ssl_context(ssl_context) {}
 +
 +asio::​ip::​tcp::​socket&​ connection::​get_socket() {
 +    return socket;
 +}
 +
 +void connection::​start() {
 +    ssl_socket = new asio::​ssl::​stream<​asio::​ip::​tcp::​socket>​(
 +        std::​move(socket),​
 +        ssl_context
 +    );
 +    handshake();​
 +}
 +
 +void connection::​handshake() {
 +        auto self(shared_from_this());​
 +        ssl_socket->​async_handshake(asio::​ssl::​stream_base::​server,​
 +        [this, self](const std::​error_code&​ error) {
 +            if (!error) {
 +                this->​read();​
 +            } else {
 +                this->​stop();​
 +            }
 +        });
 +}
 +
 +void connection::​read() {
 +    auto self(shared_from_this());​
 +    auto handler = [this, self](std::​error_code ec, std::size_t size) {
 +        if (!ec) {
 +            this->​write();​
 +        } else if (ec != asio::​error::​operation_aborted) {
 +            this->​stop();​
 +        }
 +    };
 +    std::string delimeter = "​\r\n\r\n";​
 +    asio::​async_read_until(
 +        *ssl_socket,​ asio::​dynamic_buffer(buffer),​
 +        delimeter,
 +        handler
 +    );
 +}
 +
 +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();
 +}
 +
 +void connection::​write() {
 +
 +    std::​stringstream content;
 +    for (int i = 0; i < 10000; i++) {
 +        content << "​hello!";​
 +    }
 +
 +    std::​stringstream header;
 +    header << "​HTTP/​1.1 200 OK\r\n";​
 +    header << "Date: " << timestamp() << "​\r\n";​
 +    header << "​Server:​ Srv7/​0.1\r\n";​
 +    header << "​Content-Length:​ " << content.str().length() << "​\r\n";​
 +    header << "​Content-Type:​ text/​plain\r\n";​
 +    header << "​Connection:​ close\r\n";​
 +    header << "​\r\n";​
 +
 +    response += header.str();​
 +    response += content.str();​
 +
 +    auto self(shared_from_this());​
 +    auto handler = [this, self](std::​error_code ec, std::​size_t) {
 +        if (!ec) {
 +            this->​stop();​
 +        }
 +        if (ec != asio::​error::​operation_aborted) {
 +            this->​stop();​
 +        }
 +    };
 +    asio::​async_write(
 +        *ssl_socket,​ asio::​buffer(response),​
 +        asio::​bind_executor(strand,​ handler)
 +    );
 +}
 +
 +void connection::​stop() {
 +}
 +
 +connection::​~connection() {
 +    delete ssl_socket;
 +}
 +
 +} // namespace srv7
 +
 +</​code>​
 +
 +<​file>​
 +# ab -c 200 -n100000 '​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 10000 requests
 +Completed 20000 requests
 +Completed 30000 requests
 +Completed 40000 requests
 +Completed 50000 requests
 +Completed 60000 requests
 +Completed 70000 requests
 +Completed 80000 requests
 +Completed 90000 requests
 +Completed 100000 requests
 +Finished 100000 requests
 +
 +
 +Server Software: ​       Srv7/0.1
 +Server Hostname: ​       localhost
 +Server Port:            1026
 +SSL/TLS Protocol: ​      ​TLSv1.2,​AES256-GCM-SHA384,​2048,​256
 +TLS Server Name:        localhost
 +
 +Document Path:          /
 +Document Length: ​       60000 bytes
 +
 +Concurrency Level: ​     200
 +Time taken for tests: ​  ​177.891 seconds
 +Complete requests: ​     100000
 +Failed requests: ​       0
 +Total transferred: ​     6014200000 bytes
 +HTML transferred: ​      ​6000000000 bytes
 +Requests per second: ​   562.14 [#/sec] (mean)
 +Time per request: ​      ​355.782 [ms] (mean)
 +Time per request: ​      1.779 [ms] (mean, across all concurrent requests)
 +Transfer rate:          33016.00 [Kbytes/​sec] received
 +
 +Connection Times (ms)
 +              min  mean[+/-sd] median ​  max
 +Connect: ​       7  102  60.3     ​86 ​    571
 +Processing: ​   31  253  74.7    269     613
 +Waiting: ​       2   ​34 ​ 24.3     ​26 ​    230
 +Total: ​        ​94 ​ 356  77.2    358     705
 +
 +Percentage of the requests served within a certain time (ms)
 +  50%    358
 +  66%    383
 +  75%    402
 +  80%    413
 +  90%    447
 +  95%    485
 +  98%    520
 +  99%    546
 + ​100% ​   705 (longest request)
 +
 +</​file>​
 +
 +
 +<​file>​
 +$ curl -vk '​https://​localhost:​1026/'​
 +*   ​Trying 127.0.0.1...
 +* Connected to localhost (127.0.0.1) port 1026 (#0)
 +* ALPN, offering h2
 +* ALPN, offering http/1.1
 +* Cipher selection: ALL:​!EXPORT:​!EXPORT40:​!EXPORT56:​!aNULL:​!LOW:​!RC4:​@STRENGTH
 +* successfully set certificate verify locations:
 +*   ​CAfile:​ /​usr/​local/​etc/​ca-bundle.crt
 +  CApath: none
 +* TLSv1.2 (OUT), TLS header, Certificate Status (22):
 +* TLSv1.2 (OUT), TLS handshake, Client hello (1):
 +* TLSv1.2 (IN), TLS handshake, Server hello (2):
 +* TLSv1.2 (IN), TLS handshake, Certificate (11):
 +* TLSv1.2 (IN), TLS handshake, Server finished (14):
 +* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
 +* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
 +* TLSv1.2 (OUT), TLS handshake, Finished (20):
 +* TLSv1.2 (IN), TLS change cipher, Client hello (1):
 +* TLSv1.2 (IN), TLS handshake, Finished (20):
 +* SSL connection using TLSv1.2 / AES256-GCM-SHA384
 +* ALPN, server did not agree to a protocol
 +* Server certificate:​
 +*  subject: C=US; CN=unix7.org
 +*  start date: Oct  6 07:28:03 2017 GMT
 +*  expire date: Oct  5 23:37:00 2019 GMT
 +*  issuer: C=ES; O=StartCom CA; OU=StartCom Certification Authority; CN=StartCom BR SSL ICA
 +*  SSL certificate verify result: self signed certificate in certificate chain (19), continuing anyway.
 +> GET / HTTP/1.1
 +> Host: localhost:​1026
 +> User-Agent: curl/7.48.0
 +> Accept: */*
 +
 +< HTTP/1.1 200 OK
 +< Date: Sat, 27 Apr 2019 20:31:44 UTC
 +< Server: Srv3/0.1
 +< Content-Length:​ 60000
 +< Content-Type:​ text/plain
 +< Connection: close
 +
 +hello!hello! ....
 +</​file>​