Commit Diff


commit - 901087f6637474e0688de1040c3003eaac518a76
commit + 50eb955cfb20f96fd59942b27e83fe533e1a3fcc
blob - 88b33aba9a823beaa8573b363cecf05c42860eda
blob + d970243ef62291dfabea388e5760d215099c7676
--- shared/transport.cc
+++ shared/transport.cc
@@ -14,16 +14,13 @@ namespace
 using config_t = std::unique_ptr<struct tls_config, decltype(&tls_config_free)>;
 constexpr auto protocols = TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3;
 
-constexpr auto MAX_URL_LENGTH = 1024;
-// constexpr auto MAX_META_LENGTH = 1024;
 
-
-bool read(not_null<struct tls *> ctx, std::vector<char> &buff)
+std::span<char> read(not_null<struct tls *> ctx, std::span<char> buff)
 {
     ssize_t ret{};
     for (; ; )
     {
-        ret = tls_read(ctx, &buff.at(0), buff.size());
+        ret = tls_read(ctx, &buff[0], buff.size());
         if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
             continue;
         break;
@@ -39,10 +36,9 @@ bool read(not_null<struct tls *> ctx, std::vector<char
                     error::g_log << "Error: " << last_error << std::endl;
             }
         );
-        return false;
+        return std::span<char>{};
     }
-    buff.resize(ret);
-    return true;
+    return std::span<char>{&buff[0], static_cast<size_t>(ret)};
 }
 
 
@@ -50,6 +46,9 @@ bool read(not_null<struct tls *> ctx, std::vector<char
 
 
 const std::array<char, 2> CRLF{'\r', '\n'};
+const std::array<char, 1> SPACE{' '};
+const std::array<char, 2> STATUS_20_SUCCESS{'2', '0'};
+const std::array<char, 2> STATUS_59_BAD_REQUEST{'5', '9'};
 
 
 bool init()
@@ -143,30 +142,71 @@ void accept(
 }
 
 
-bool read_request(not_null<struct tls *> ctx, std::vector<char> &url)
+std::span<char> read_request(not_null<struct tls *> ctx, std::array<char, MAX_REQUEST_LENGTH> &buffer)
 {
-    url.resize(MAX_URL_LENGTH + CRLF.size());
+    const auto request = read(ctx, buffer);
+    if (!request.size())
+        return std::span<char>{};
 
-    if (!read(ctx, url))
-        return false;
-
-    for (auto current = url.cbegin(); current < url.end(); ++current)
+    for (auto current = request.begin(); current < request.end(); ++current)
     {
         const auto next = (current + 1);
-        if (next == url.end())
+        if (next == request.end())
             break;
 
         if (*current == CRLF[0] && *next == CRLF[1])
         {
             // > servers MUST ignore anything sent after the first occurrence of a <CR><LF>.
-            url.resize(current - url.cbegin());
-            return true;
+            return std::span<char>{&request[0], static_cast<size_t>(current - request.begin())};
         }
     }
     error::occurred("Parse request", error::none{});
-    return false;
+    return std::span<char>{};
 }
 
 
+bool send_response(not_null<struct tls *> ctx, std::array<char, 2> status, std::span<const char> meta)
+{
+    // > <STATUS><SPACE><META><CR><LF>
+    std::array<char, status.size() + SPACE.size() + MAX_META_LENGTH + CRLF.size()> buff;
+    auto current = buff.begin();
+    current = std::copy(status.cbegin(), status.cend(), current);
+    current = std::copy(SPACE.cbegin(), SPACE.cend(), current);
+    assert(meta.size() <= MAX_META_LENGTH);
+    current = std::copy(meta.begin(), meta.end(), current);
+    current = std::copy(CRLF.cbegin(), CRLF.cend(), current);
+
+    return send(ctx, std::span<char const>{&buff[0], static_cast<size_t>(current - buff.begin())});
+}
+
+
+bool send(not_null<struct tls *> ctx, std::span<const char> buff)
+{
+    ssize_t ret{0};
+    while (buff.size() > 0)
+    {
+        ret = tls_write(ctx, &buff[0], buff.size());
+        if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
+            continue;
+        break;
+        buff = std::span<const char>{&buff[ret], buff.size() - ret};
+    }
+    if (ret == -1)
+    {
+        const auto last_error = tls_error(ctx);
+        error::occurred(
+            "TLS write",
+            [last_error]
+            {
+                if (last_error)
+                    error::g_log << "Error: " << last_error << std::endl;
+            }
+        );
+        return false;
+    }
+    return true;
+}
+
+
 }   // namespace transport
 }   // namespace vostok
blob - 28299ad9baa535fb9945cd2423e1c0eef48915c4
blob + 73cdc0d8a6131b9393b5dcd70420546bffd0f6a3
--- shared/transport.h
+++ shared/transport.h
@@ -4,11 +4,11 @@
 #include <not_null>
 #include <non_copiable>
 #include <unique_fd>
-#include <tuple>
-#include <tls.h>
+
 #include <array>
-#include <vector>
+#include <span>
 
+#include <tls.h>
 
 #pragma once
 
@@ -19,25 +19,30 @@ namespace transport
 {
 
 
+constexpr auto MAX_URL_LENGTH = 1024;
+constexpr auto MAX_META_LENGTH = 1024;
+
 extern const std::array<char, 2> CRLF;
+extern const std::array<char, 2> STATUS_20_SUCCESS;
+extern const std::array<char, 2> STATUS_59_BAD_REQUEST;
 
 
+constexpr auto MAX_REQUEST_LENGTH = MAX_URL_LENGTH + CRLF.size();
+
 /** `struct tls *` smart pointer */
 typedef std::unique_ptr<struct tls, decltype(&tls_free)> context_t;
 
 
-/** Per-process initialization */
-bool init();
-
-
-/* Create new TLS context for gemini server */
-void create_server(not_null<czstring> cert_file, not_null<czstring> key_file, context_t &ctx);
-
-
+/** Pair: `struct tls *` pointer and file descriptor as smart pointer */
 class accepted_context : non_copiable
 {
 public:
-    typedef std::tuple< int, struct tls *> value;
+    struct value
+    {
+        int m_fd{-1};
+        struct tls *m_ctx{nullptr};
+    };
+
     accepted_context(int fd = -1, struct tls *ctx = nullptr) { reset(value{fd, ctx}); }
     accepted_context(value v) { reset(v); }
 
@@ -48,8 +53,8 @@ class accepted_context : non_copiable (public)
     value release() { return value{m_fd.release(), m_ctx.release()}; }
     void reset(value v=value{-1, nullptr})
     {
-        m_fd.reset(std::get<0>(v));
-        m_ctx.reset(std::get<1>(v));
+        m_fd.reset(v.m_fd);
+        m_ctx.reset(v.m_ctx);
         assert((m_fd && m_ctx) || (!m_fd && !m_ctx));
     }
     operator bool () const { return m_fd && m_ctx; }
@@ -59,6 +64,15 @@ class accepted_context : non_copiable (public)
     context_t m_ctx{nullptr, tls_free};
 };
 
+
+/** Per-process initialization */
+bool init();
+
+
+/* Create new TLS context for gemini server */
+void create_server(not_null<czstring> cert_file, not_null<czstring> key_file, context_t &ctx);
+
+
 /** Accept new client. Socket ownership transfer */
 void accept(
     not_null<struct tls *>server_ctx,
@@ -67,9 +81,17 @@ void accept(
 );
 
 
-/** Read genimi request and return url */
-bool read_request(not_null<struct tls *> ctx, std::vector<char> &url);
+/** Read genimi request and return url (empty url - error) */
+std::span<char> read_request(not_null<struct tls *> ctx, std::array<char, MAX_REQUEST_LENGTH> &buffer);
 
 
+/** Write gemini response */
+bool send_response(not_null<struct tls *> ctx, std::array<char, 2> status, std::span<const char> meta);
+
+
+/** Send raw bytes */
+bool send(not_null<struct tls *> ctx, std::span<const char> buff);
+
+
 }   // namespace transport
 }   // namespace vostok
blob - 7455e658ed0f78bb460d284f6809c819fde5b551
blob + 50d12fd6adfe4e61ca5b62655d187117f60b628d
--- vostokd/Makefile
+++ vostokd/Makefile
@@ -1,4 +1,4 @@
-CXXFLAGS	= -Wall -Wextra -std=c++11
+CXXFLAGS	= -Wall -Wextra -std=c++20
 CXXFLAGS	+= -I${.CURDIR}/../shared
 CXXFLAGS	+= -ltls
 
blob - b6b52626e864f4106e5f532d51e7c851a32015be
blob + 6e2205ba496a0fb0ece746868d149c26fdae1681
--- vostokd/vostokd.cc
+++ vostokd/vostokd.cc
@@ -18,7 +18,20 @@ namespace vostok
 namespace
 {
 
+template <std::size_t N>
+constexpr std::span<const char> literal_to_span(const char (&arr)[N])
+{
+    static_assert(N > 0);
+    return std::span<const char>{&arr[0], N - 1};
+}
 
+namespace meta
+{
+const auto badrequest = literal_to_span("Bad request");
+const auto textgemini = literal_to_span("text/gemini");
+}   // namespace meta
+
+
 struct command_line_arguments
 {
     not_null<czstring> m_addr{"127.0.0.1"};
@@ -95,18 +108,22 @@ void client_thread(transport::accepted_context::value 
     const transport::accepted_context ctx{raw_value};
     assert(ctx);
 
-    std::vector<char> url;
-    if (!transport::read_request(ctx.get_ctx(), url))
+    std::array<char, transport::MAX_REQUEST_LENGTH> buffer;
+    const auto url = transport::read_request(ctx.get_ctx(), buffer);
+
+    for (auto c : url)
+        error::g_log << c;
+    error::g_log << std::endl;
+
+    if (!url.size())
     {
-        // transport::send_response
+        transport::send_response(ctx.get_ctx(), transport::STATUS_59_BAD_REQUEST, meta::badrequest);
         return;
     }
 
-    error::g_log << "Request URL: \"";
-    for (const char c : url)
-        error::g_log << c;
-    error::g_log << "\"" << std::endl;
-
+    transport::send_response(ctx.get_ctx(), transport::STATUS_20_SUCCESS, meta::textgemini);
+    const char content[] = "# Vostok server\r\n\r\n...work-in-progress...";
+    transport::send(ctx.get_ctx(), literal_to_span(content));
 }