added suport for embedding resources and added serving /favicon.ico

Dieser Commit ist enthalten in:
Reinder Feenstra 2024-12-30 23:30:01 +01:00
Ursprung 8c2d89cdc6
Commit de65905387
4 geänderte Dateien mit 177 neuen und 5 gelöschten Zeilen

Datei anzeigen

@ -203,6 +203,21 @@ if(DEFINED ENV{VCPKG_ROOT})
include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
endif()
### RESOURCES ###
include(cmake/add-resource.cmake)
add_resource(resource-shared
BASE_DIR ../
FILES
shared/gfx/appicon.ico
)
add_dependencies(traintastic-server resource-shared)
if(BUILD_TESTING)
add_dependencies(traintastic-server-test resource-shared)
endif()
### OPTIONS ###
if(NO_LOCALHOST_ONLY_SETTING)

Datei anzeigen

@ -0,0 +1,40 @@
#
# This file is part of the traintastic source code.
# See <https://github.com/traintastic/traintastic>.
#
# Copyright (C) 2024 Reinder Feenstra
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
function(add_resource TARGET_NAME)
cmake_parse_arguments(PARSE_ARG "" "BASE_DIR" "FILES" ${ARGN})
if(PARSE_ARG_BASE_DIR)
set(PARSE_ARG_BASE_DIR "${CMAKE_SOURCE_DIR}/${PARSE_ARG_BASE_DIR}")
else()
set(PARSE_ARG_BASE_DIR "${CMAKE_SOURCE_DIR}")
endif()
foreach(INPUT_FILE ${PARSE_ARG_FILES})
set(OUTPUT_FILE ${CMAKE_BINARY_DIR}/resource/${INPUT_FILE}.hpp)
add_custom_command(
OUTPUT ${OUTPUT_FILE}
COMMAND Python3::Interpreter ${CMAKE_SOURCE_DIR}/cmake/generateresourceheader.py ${PARSE_ARG_BASE_DIR} ${INPUT_FILE} ${OUTPUT_FILE}
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/generateresourceheader.py ${PARSE_ARG_BASE_DIR}/${INPUT_FILE}
COMMENT "Generating resource header resource/${INPUT_FILE}.hpp"
)
list(APPEND OUTPUT_HEADERS ${OUTPUT_FILE})
endforeach()
add_custom_target(${TARGET_NAME} ALL DEPENDS ${OUTPUT_HEADERS})
endfunction()

Datei anzeigen

@ -0,0 +1,84 @@
#
# This file is part of the traintastic source code.
# See <https://github.com/traintastic/traintastic>.
#
# Copyright (C) 2024 Reinder Feenstra
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
import sys
import os
import re
import textwrap
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <input dir> <input file> <output header>")
sys.exit(1)
input_file = os.path.join(sys.argv[1], sys.argv[2])
input_file_ext = os.path.splitext(input_file)[1]
namespaces = ['Resource'] + os.path.dirname(sys.argv[2]).replace('../', '').split('/')
variable = re.sub(r'[\.]+','_', os.path.basename(sys.argv[2]).lower())
guard = '_'.join(namespaces).upper() + '_' + re.sub(r'[\.]+','_', os.path.basename(sys.argv[3]).upper())
is_binary = input_file_ext not in ['html', 'css', 'js']
with open(input_file, 'rb') as f:
contents = f.read()
if is_binary:
size = len(contents)
contents = ', '.join(['std::byte{' + str(by) + '}' for by in contents])
contents = '\n '.join(textwrap.wrap(contents, width=120))
with open(sys.argv[3], 'w') as f:
f.write(f'''// Auto-generated, do not edit, it will be overwritten
#ifndef {guard}
#define {guard}
#include <array>
namespace {'::'.join(namespaces)}
{{
constexpr std::array<std::byte, {size}> {variable}{{{{
{contents}
}}}};
}}
#endif
''')
else: # text
with open(sys.argv[3], 'w') as f:
f.write(f'''// Auto-generated, do not edit, it will be overwritten
#ifndef {guard}
#define {guard}
#include <string_view>
namespace {'::'.join(namespaces)}
{{
constexpr std::string_view {variable} = R"({contents})";
}}
#endif
''')

Datei anzeigen

@ -21,6 +21,8 @@
*/
#include "server.hpp"
#include <boost/beast/http/buffer_body.hpp>
#include <tcb/span.hpp>
#include <traintastic/network/message.hpp>
#include <version.hpp>
#include "connection.hpp"
@ -29,6 +31,7 @@
#include "../log/log.hpp"
#include "../log/logmessageexception.hpp"
#include "../utils/setthreadname.hpp"
#include <resource/shared/gfx/appicon.ico.hpp>
#define IS_SERVER_THREAD (std::this_thread::get_id() == m_thread.get_id())
@ -40,7 +43,8 @@ namespace
static constexpr std::string_view serverHeader{"Traintastic-server/" TRAINTASTIC_VERSION_FULL};
static constexpr std::string_view contentTypeTextPlain{"text/plain"};
static constexpr std::string_view contentTypeTextHtml{"text/Html"};
static constexpr std::string_view contentTypeTextHtml{"text/html"};
static constexpr std::string_view contentTypeImageXIcon{"image/x-icon"};
http::message_generator notFound(const http::request<http::string_body>& request)
{
@ -70,6 +74,30 @@ http::message_generator methodNotAllowed(const http::request<http::string_body>&
return response;
}
http::message_generator binary(const http::request<http::string_body>& request, std::string_view contentType, tcb::span<const std::byte> body)
{
if(request.method() != http::verb::get && request.method() != http::verb::head)
{
return methodNotAllowed(request, {http::verb::get, http::verb::head});
}
http::response<http::buffer_body> response{http::status::ok, request.version()};
response.set(http::field::server, serverHeader);
response.set(http::field::content_type, contentType);
response.keep_alive(request.keep_alive());
if(request.method() == http::verb::head)
{
response.content_length(body.size());
}
else
{
response.body().data = const_cast<std::byte*>(body.data());
response.body().size = body.size();
}
response.body().more = false;
response.prepare_payload();
return response;
}
http::message_generator text(const http::request<http::string_body>& request, std::string_view contentType, std::string_view body)
{
if(request.method() != http::verb::get && request.method() != http::verb::head)
@ -288,10 +316,11 @@ void Server::doAccept()
http::message_generator Server::handleHTTPRequest(http::request<http::string_body>&& request)
{
if(request.target() == "/")
const auto target = request.target();
if(target == "/")
{
return textHtml(request,
"<!doctype html>"
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"utf-8\">"
@ -299,11 +328,15 @@ http::message_generator Server::handleHTTPRequest(http::request<http::string_bod
"<title>Traintastic v" TRAINTASTIC_VERSION_FULL "</title>"
"</head>"
"<body>"
"<h1>Traintastic v" TRAINTASTIC_VERSION_FULL "</h1>"
"<h1>Traintastic <small>v" TRAINTASTIC_VERSION_FULL "</small></h1>"
"</body>"
"</html>");
}
if(request.target() == "/version")
if(target == "/favicon.ico")
{
return binary(request, contentTypeImageXIcon, Resource::shared::gfx::appicon_ico);
}
if(target == "/version")
{
return textPlain(request, TRAINTASTIC_VERSION_FULL);
}