-
-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/wasm remote 2 #1288
base: master
Are you sure you want to change the base?
Feature/wasm remote 2 #1288
Changes from 13 commits
74023d8
c035986
e2d4b35
e6028a2
b9f71f6
de1831f
c43b2aa
2304b1a
703d9b7
ed8d1aa
0d54c99
49bb138
c231ef0
0fd3ef5
c8b914c
819619e
48f0036
be79bdb
b1914f3
ab4893e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
*.zip | ||
build-* | ||
AppRun* | ||
*.AppImage | ||
*.txz | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ set(HDRS | |
"RemoteControl/DocumentPlugin.hpp" | ||
"i-score-remote/RemoteApplication.hpp" | ||
"score_plugin_remotecontrol.hpp" | ||
"RemoteControl/Http_server.hpp" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you name that file HttpServer in line with the rest of score ? |
||
) | ||
set(SRCS | ||
"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Settings/Model.cpp" | ||
|
@@ -41,12 +42,16 @@ set(SRCS | |
"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/ApplicationPlugin.cpp" | ||
"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/DocumentPlugin.cpp" | ||
|
||
"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Http_server.cpp" | ||
|
||
"${CMAKE_CURRENT_SOURCE_DIR}/score_plugin_remotecontrol.cpp" | ||
) | ||
|
||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/build-wasm/ DESTINATION ${CMAKE_BINARY_DIR}/src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we won't need that: the remote's .wasm file is not supposed to be built as part of score but in a different repo which will be in the package manager |
||
|
||
add_library(${PROJECT_NAME} ${SRCS} ${HDRS} ${QRCS}) | ||
|
||
target_link_libraries(${PROJECT_NAME} PUBLIC score_plugin_scenario score_plugin_js ${QT_PREFIX}::WebSockets) | ||
target_link_libraries(${PROJECT_NAME} PUBLIC score_plugin_scenario score_plugin_js ${QT_PREFIX}::WebSockets ossia) | ||
|
||
setup_score_plugin(${PROJECT_NAME}) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
#pragma once | ||
#include <score/plugins/application/GUIApplicationPlugin.hpp> | ||
|
||
#include <RemoteControl/Http_server.hpp> | ||
namespace RemoteControl | ||
{ | ||
class ApplicationPlugin final : public score::GUIApplicationPlugin | ||
|
@@ -10,5 +11,6 @@ class ApplicationPlugin final : public score::GUIApplicationPlugin | |
|
||
protected: | ||
void on_createdDocument(score::Document& doc) override; | ||
Http_server m_server; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HttpServer |
||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,325 @@ | ||
// | ||
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) | ||
// | ||
// Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
// | ||
// Official repository: https://github.com/boostorg/beast | ||
// | ||
|
||
#include <RemoteControl/Http_server.hpp> | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
namespace RemoteControl | ||
{ | ||
|
||
Http_server::Http_server() | ||
{ | ||
// m_docRoot = "/tmp"; | ||
} | ||
|
||
Http_server::~Http_server() | ||
{ | ||
shutdown(m_listenSocket, SHUT_RDWR); | ||
ioc.stop(); | ||
m_serverThread.join(); | ||
} | ||
|
||
// Return a reasonable mime type based on the extension of a file. | ||
beast::string_view | ||
Http_server::mime_type(beast::string_view path) | ||
{ | ||
using beast::iequals; | ||
auto const ext = [&path] | ||
{ | ||
auto const pos = path.rfind("."); | ||
if(pos == beast::string_view::npos) | ||
return beast::string_view{}; | ||
return path.substr(pos); | ||
}(); | ||
if(iequals(ext, ".htm")) return "text/html"; | ||
if(iequals(ext, ".html")) return "text/html"; | ||
if(iequals(ext, ".php")) return "text/html"; | ||
if(iequals(ext, ".css")) return "text/css"; | ||
if(iequals(ext, ".txt")) return "text/plain"; | ||
if(iequals(ext, ".js")) return "application/javascript"; | ||
if(iequals(ext, ".json")) return "application/json"; | ||
if(iequals(ext, ".xml")) return "application/xml"; | ||
if(iequals(ext, ".swf")) return "application/x-shockwave-flash"; | ||
if(iequals(ext, ".flv")) return "video/x-flv"; | ||
if(iequals(ext, ".png")) return "image/png"; | ||
if(iequals(ext, ".jpe")) return "image/jpeg"; | ||
if(iequals(ext, ".jpeg")) return "image/jpeg"; | ||
if(iequals(ext, ".jpg")) return "image/jpeg"; | ||
if(iequals(ext, ".gif")) return "image/gif"; | ||
if(iequals(ext, ".bmp")) return "image/bmp"; | ||
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; | ||
if(iequals(ext, ".tiff")) return "image/tiff"; | ||
if(iequals(ext, ".tif")) return "image/tiff"; | ||
if(iequals(ext, ".svg")) return "image/svg+xml"; | ||
if(iequals(ext, ".svgz")) return "image/svg+xml"; | ||
return "application/text"; | ||
} | ||
|
||
// Append an HTTP rel-path to a local filesystem path. | ||
// The returned path is normalized for the platform. | ||
std::string | ||
Http_server::path_cat( | ||
beast::string_view base, | ||
beast::string_view path) | ||
{ | ||
if (base.empty()) | ||
return std::string(path); | ||
std::string result(base); | ||
#ifdef BOOST_MSVC | ||
char constexpr path_separator = '\\'; | ||
if(result.back() == path_separator) | ||
result.resize(result.size() - 1); | ||
result.append(path.data(), path.size()); | ||
for(auto& c : result) | ||
if(c == '/') | ||
c = path_separator; | ||
#else | ||
char constexpr path_separator = '/'; | ||
if(result.back() == path_separator) | ||
result.resize(result.size() - 1); | ||
result.append(path.data(), path.size()); | ||
#endif | ||
return result; | ||
} | ||
|
||
// This function produces an HTTP response for the given | ||
// request. The type of the response object depends on the | ||
// contents of the request, so the interface requires the | ||
// caller to pass a generic lambda for receiving the response. | ||
template< | ||
class Body, class Allocator, | ||
class Send> | ||
void | ||
Http_server::handle_request( | ||
beast::string_view doc_root, | ||
http::request<Body, http::basic_fields<Allocator>>&& req, | ||
Send&& send) | ||
{ | ||
// Returns a bad request response | ||
auto const bad_request = | ||
[&req](beast::string_view why) | ||
{ | ||
http::response<http::string_body> res{http::status::bad_request, req.version()}; | ||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); | ||
res.set(http::field::content_type, "text/html"); | ||
res.keep_alive(req.keep_alive()); | ||
res.body() = std::string(why); | ||
res.prepare_payload(); | ||
return res; | ||
}; | ||
|
||
// Returns a not found response | ||
auto const not_found = | ||
[&req](beast::string_view target) | ||
{ | ||
http::response<http::string_body> res{http::status::not_found, req.version()}; | ||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); | ||
res.set(http::field::content_type, "text/html"); | ||
res.keep_alive(req.keep_alive()); | ||
res.body() = "The resource '" + std::string(target) + "' was not found.y<br> Go to the following address : http://ip_address:port/remote.html."; | ||
res.prepare_payload(); | ||
return res; | ||
}; | ||
|
||
// Returns a server error response | ||
auto const server_error = | ||
[&req](beast::string_view what) | ||
{ | ||
http::response<http::string_body> res{http::status::internal_server_error, req.version()}; | ||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); | ||
res.set(http::field::content_type, "text/html"); | ||
res.keep_alive(req.keep_alive()); | ||
res.body() = "An error occurred: '" + std::string(what) + "'"; | ||
res.prepare_payload(); | ||
return res; | ||
}; | ||
|
||
// Make sure we can handle the method | ||
if( req.method() != http::verb::get && | ||
req.method() != http::verb::head) | ||
return send(bad_request("Unknown HTTP-method")); | ||
|
||
// Request path must be absolute and not contain "..". | ||
if( req.target().empty() || | ||
req.target()[0] != '/' || | ||
req.target().find("..") != beast::string_view::npos) | ||
return send(bad_request("Illegal request-target")); | ||
|
||
// Build the path to the requested file | ||
std::string path = path_cat(doc_root, req.target()); | ||
if(req.target().back() == '/') | ||
path.append("index.html"); | ||
|
||
// Attempt to open the file | ||
beast::error_code ec; | ||
http::file_body::value_type body; | ||
body.open(path.c_str(), beast::file_mode::scan, ec); | ||
|
||
// Handle the case where the file doesn't exist | ||
if(ec == beast::errc::no_such_file_or_directory) | ||
return send(not_found(req.target())); | ||
|
||
// Handle an unknown error | ||
if(ec) | ||
return send(server_error(ec.message())); | ||
|
||
// Cache the size since we need it after the move | ||
auto const size = body.size(); | ||
|
||
// Respond to HEAD request | ||
if(req.method() == http::verb::head) | ||
{ | ||
http::response<http::empty_body> res{http::status::ok, req.version()}; | ||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); | ||
res.set(http::field::content_type, mime_type(path)); | ||
res.content_length(size); | ||
res.keep_alive(false); | ||
return send(std::move(res)); | ||
} | ||
|
||
// Respond to GET request | ||
http::response<http::file_body> res{ | ||
std::piecewise_construct, | ||
std::make_tuple(std::move(body)), | ||
std::make_tuple(http::status::ok, req.version())}; | ||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); | ||
res.set(http::field::content_type, mime_type(path)); | ||
res.content_length(size); | ||
res.keep_alive(false); | ||
return send(std::move(res)); | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
// Report a failure | ||
void | ||
Http_server::fail(beast::error_code ec, char const* what) | ||
{ | ||
std::cerr << what << ": " << ec.message() << "\n"; | ||
} | ||
|
||
// Handles an HTTP server connection | ||
void | ||
Http_server::do_session( | ||
tcp::socket& socket, | ||
std::shared_ptr<std::string const> const& doc_root) | ||
{ | ||
bool close = false; | ||
beast::error_code ec; | ||
|
||
// This buffer is required to persist across reads | ||
beast::flat_buffer buffer; | ||
|
||
// This lambda is used to send messages | ||
send_lambda<tcp::socket> lambda{socket, close, ec}; | ||
|
||
for(;;) | ||
{ | ||
// Read a request | ||
http::request<http::string_body> req; | ||
http::read(socket, buffer, req, ec); | ||
if(ec == http::error::end_of_stream) | ||
break; | ||
if(ec) | ||
return Http_server::fail(ec, "read"); | ||
|
||
// Send the response | ||
Http_server::handle_request(*doc_root, std::move(req), lambda); | ||
if(ec) | ||
return Http_server::fail(ec, "write"); | ||
if(close) | ||
{ | ||
// This means we should close the connection, usually because | ||
// the response indicated the "Connection: close" semantic. | ||
break; | ||
} | ||
} | ||
|
||
// Send a TCP shutdown | ||
socket.shutdown(tcp::socket::shutdown_send, ec); | ||
|
||
// At this point the connection is closed gracefully | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
// Set the IP address in the remote.html file | ||
void | ||
Http_server::set_ip_address(std::string address) | ||
{ | ||
std::rename("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html", | ||
"./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html~"); | ||
|
||
std::ifstream old_file("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html~"); | ||
std::ofstream new_file("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html"); | ||
|
||
std::string addr = "\"" + address + "\""; | ||
|
||
for( std::string contents_of_file; std::getline(old_file, contents_of_file); ) { | ||
std::string::size_type position = contents_of_file.find("%SCORE_IP_ADDRESS%"); | ||
if( position != std::string::npos ) | ||
contents_of_file = contents_of_file.replace(position, 18, addr); | ||
new_file << contents_of_file << '\n'; | ||
} | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
// Launch the open_server function in a thread | ||
void | ||
Http_server::start_thread() | ||
{ | ||
m_serverThread = std::thread{[this] { open_server(); }}; | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
// Open a server using sockets | ||
int | ||
Http_server::open_server() | ||
{ | ||
try | ||
{ | ||
auto const address2 = net::ip::make_address("0.0.0.0"); | ||
auto const port = static_cast<unsigned short>(std::atoi("8080")); | ||
auto const m_docRoot = std::make_shared<std::string>("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this line only works on the build computer.
|
||
|
||
bool is_ip_address_set = false; | ||
|
||
// The acceptor receives incoming connections | ||
tcp::acceptor acceptor{ioc, {address2, port}}; | ||
m_listenSocket = acceptor.native_handle(); | ||
for(;;) | ||
{ | ||
// This will receive the new connection | ||
tcp::socket socket{ioc}; | ||
|
||
// Block until we get a connection | ||
acceptor.accept(socket); | ||
|
||
// Set ip address | ||
if(!is_ip_address_set) | ||
{ | ||
set_ip_address(socket.local_endpoint().address().to_string()); | ||
is_ip_address_set = true; | ||
} | ||
|
||
// Launch the session, transferring ownership of the socket | ||
do_session(socket, m_docRoot); | ||
} | ||
} | ||
catch (const std::exception& e) | ||
{ | ||
std::cerr << "Error: " << e.what() << std::endl; | ||
return EXIT_FAILURE; | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's remove that part, we don't want this as a submodule