NEW: add http server in BambuStudio

Change-Id: I72d99277187ea0d6b600e741dcc4306bc5f44036
Signed-off-by: Stone Li <stone.li@bambulab.com>
(cherry picked from commit 9ee36044158856e433b19e407094120bd15f4aa0)
This commit is contained in:
Stone Li 2023-01-28 11:18:12 +08:00 committed by Lane.Wei
parent 5cbea66058
commit cb2334559b
10 changed files with 397 additions and 23 deletions

View file

@ -391,7 +391,7 @@ IM_MSVC_RUNTIME_CHECKS_OFF
static inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision
static inline double ImPow(double x, double y) { return pow(x, y); }
static inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision
static inline double ImLog(double x) { return log(x); }
static inline double ImLog(double x) { return logf(x); }
static inline int ImAbs(int x) { return x < 0 ? -x : x; }
static inline float ImAbs(float x) { return fabsf(x); }
static inline double ImAbs(double x) { return fabs(x); }

View file

@ -273,6 +273,8 @@ set(SLIC3R_GUI_SOURCES
GUI/ImGuiWrapper.cpp
GUI/DeviceManager.hpp
GUI/DeviceManager.cpp
GUI/HttpServer.hpp
GUI/HttpServer.cpp
Config/Snapshot.cpp
Config/Snapshot.hpp
Config/Version.cpp

View file

@ -87,7 +87,6 @@
//BBS: DailyTip and UserGuide Dialog
#include "WebDownPluginDlg.hpp"
#include "WebGuideDialog.hpp"
#include "WebUserLoginDialog.hpp"
#include "ReleaseNote.hpp"
#include "ModelMall.hpp"
@ -2369,9 +2368,6 @@ bool GUI_App::on_init_inner()
}
//}
if (app_config->get("sync_user_preset") == "true") {
//BBS loading user preset
// Always async, not such startup step
@ -3103,14 +3099,24 @@ void GUI_App::ShowDownNetPluginDlg() {
}
}
void GUI_App::ShowUserLogin()
void GUI_App::ShowUserLogin(bool show)
{
// BBS: User Login Dialog
if (show) {
try {
ZUserLogin LoginDlg;
LoginDlg.ShowModal();
if (!login_dlg)
login_dlg = new ZUserLogin();
else {
delete login_dlg;
login_dlg = new ZUserLogin();
}
login_dlg->ShowModal();
} catch (std::exception &e) {
// wxMessageBox(e.what(), "", MB_OK);
;
}
} else {
if (login_dlg)
login_dlg->EndModal(wxID_OK);
}
}
@ -3393,11 +3399,6 @@ std::string GUI_App::handle_web_request(std::string cmd)
std::string web_cmd = j["command"].get<std::string>();
if (web_cmd == "request_model_download") {
/* json j_data = j["data"];
json import_j;*/
/* import_j["model_id"] = j["data"]["model_id"].get<std::string>();
import_j["profile_id"] = j["data"]["profile_id"].get<std::string>();*/
std::string download_url = "";
if (j["data"].contains("download_url"))
download_url = j["data"]["download_url"].get<std::string>();
@ -4161,6 +4162,16 @@ void GUI_App::stop_sync_user_preset()
m_sync_update_thread.join();
}
void GUI_App::start_http_server()
{
if (!m_http_server.is_started())
m_http_server.start();
}
void GUI_App::stop_http_server()
{
m_http_server.stop();
}
bool GUI_App::switch_language()
{
if (select_language()) {

View file

@ -11,8 +11,10 @@
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/GUI/WebViewDialog.hpp"
#include "slic3r/GUI/WebUserLoginDialog.hpp"
#include "slic3r/GUI/HMS.hpp"
#include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp"
#include "slic3r/GUI/HttpServer.hpp"
#include "../Utils/PrintHost.hpp"
#include <wx/app.h>
@ -274,6 +276,9 @@ private:
bool m_networking_cancel_update { false };
std::shared_ptr<UpgradeNetworkJob> m_upgrade_network_job;
// login widget
ZUserLogin* login_dlg { nullptr };
VersionInfo version_info;
static std::string version_display;
HMSQuery *hms_query { nullptr };
@ -283,6 +288,7 @@ private:
bool m_is_dark_mode{ false };
bool m_adding_script_handler { false };
bool m_side_popup_status{false};
HttpServer m_http_server;
public:
void check_filaments_in_blacklist(std::string tag_supplier, std::string tag_material, bool& in_blacklist, std::string& action, std::string& info);
std::string get_local_models_path();
@ -381,7 +387,7 @@ public:
wxString transition_tridid(int trid_id);
void ShowUserGuide();
void ShowDownNetPluginDlg();
void ShowUserLogin();
void ShowUserLogin(bool show = true);
void ShowOnlyFilament();
//BBS
void request_login(bool show_user_info = false);
@ -424,6 +430,8 @@ public:
void sync_preset(Preset* preset);
void start_sync_user_preset(bool load_immediately = false, bool with_progress_dlg = false);
void stop_sync_user_preset();
void start_http_server();
void stop_http_server();
static bool catch_error(std::function<void()> cb, const std::string& err);

View file

@ -0,0 +1,151 @@
#include "HttpServer.hpp"
#include <boost/log/trivial.hpp>
#include "GUI_App.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
namespace Slic3r {
namespace GUI {
static std::string parse_params(std::string url, std::string key)
{
size_t start = url.find(key);
if (start < 0) return "";
size_t eq = url.find('=', start);
if (eq < 0) return "";
std::string key_str = url.substr(start, eq - start);
if (key_str != key)
return "";
start += key.size() + 1;
size_t end = url.find('&', start);
if (end < 0)
return "";
std::string result = url.substr(start, end - start);
return result;
}
std::string http_headers::get_response()
{
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_response";
std::stringstream ssOut;
std::string url_str = Http::url_decode(url);
if (boost::contains(url_str, "access_token")) {
std::string sHTML = "<html><body><p>redirect to url </p></body></html>";
std::string redirect_url = parse_params(url_str, "redirect_url");
std::string access_token = parse_params(url_str, "access_token");
std::string refresh_token = parse_params(url_str, "refresh_token");
std::string expires_in_str = parse_params(url_str, "expires_in");
std::string refresh_expires_in_str = parse_params(url_str, "refresh_expires_in");
NetworkAgent* agent = wxGetApp().getAgent();
unsigned int http_code;
std::string http_body;
int result = agent->get_my_profile(access_token, &http_code, &http_body);
if (result == 0) {
std::string user_id;
std::string user_name;
std::string user_account;
std::string user_avatar;
try {
json user_j = json::parse(http_body);
if (user_j.contains("uid"))
user_id = std::to_string(user_j["uid"].get<int>());
if (user_j.contains("name"))
user_name = user_j["name"].get<std::string>();
if (user_j.contains("avatar"))
user_avatar = user_j["avatar"].get<std::string>();
if (user_j.contains("account"))
user_account = user_j["account"].get<std::string>();
} catch (...) {
;
}
json j;
j["data"]["refresh_token"] = refresh_token;
j["data"]["token"] = access_token;
j["data"]["expires_in"] = expires_in_str;
j["data"]["refresh_expires_in"] = refresh_expires_in_str;
j["data"]["user"]["uid"] = user_id;
j["data"]["user"]["name"] = user_name;
j["data"]["user"]["account"] = user_account;
j["data"]["user"]["avatar"] = user_avatar;
agent->change_user(j.dump());
if (agent->is_user_login()) {
wxGetApp().request_user_login(1);
}
GUI::wxGetApp().CallAfter([this] {
wxGetApp().ShowUserLogin(false);
});
std::string location_str = (boost::format("Location: %1%?result=success") % redirect_url).str();
ssOut << "HTTP/1.1 302 Found" << std::endl;
ssOut << location_str << std::endl;
ssOut << "content-type: text/html" << std::endl;
ssOut << "content-length: " << sHTML.length() << std::endl;
ssOut << std::endl;
ssOut << sHTML;
} else {
std::string error_str = "get_user_profile_error_" + std::to_string(result);
std::string location_str = (boost::format("Location: %1%?result=fail&error=%2%") % redirect_url % error_str).str();
ssOut << "HTTP/1.1 302 Found" << std::endl;
ssOut << location_str << std::endl;
ssOut << "content-type: text/html" << std::endl;
ssOut << "content-length: " << sHTML.length() << std::endl;
ssOut << std::endl;
ssOut << sHTML;
}
} else {
std::string sHTML = "<html><body><h1>404 Not Found</h1><p>There's nothing here.</p></body></html>";
ssOut << "HTTP/1.1 404 Not Found" << std::endl;
ssOut << "content-type: text/html" << std::endl;
ssOut << "content-length: " << sHTML.length() << std::endl;
ssOut << std::endl;
ssOut << sHTML;
}
return ssOut.str();
}
void accept_and_run(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& io_service)
{
std::shared_ptr<session> sesh = std::make_shared<session>(io_service);
acceptor.async_accept(sesh->socket,
[sesh, &acceptor, &io_service](const boost::beast::error_code& accept_error)
{
accept_and_run(acceptor, io_service);
if (!accept_error)
{
session::interact(sesh);
}
});
}
HttpServer::HttpServer()
{
;
}
void HttpServer::start()
{
BOOST_LOG_TRIVIAL(info) << "start_http_service...";
start_http_server = true;
m_http_server_thread = Slic3r::create_thread(
[this] {
boost::asio::io_service io_service;
boost::asio::ip::tcp::endpoint endpoint{ boost::asio::ip::tcp::v4(), 9090};
boost::asio::ip::tcp::acceptor acceptor { io_service, endpoint};
acceptor.listen();
accept_and_run(acceptor, io_service);
while (start_http_server) {
io_service.run();
}
});
}
void HttpServer::stop()
{
start_http_server = false;
if (m_http_server_thread.joinable())
m_http_server_thread.join();
}
} // GUI
} //Slic3r

View file

@ -0,0 +1,162 @@
#ifndef slic3r_Http_App_hpp_
#define slic3r_Http_App_hpp_
#include <mutex>
#include <stack>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <string>
#include <memory>
using namespace boost;
using namespace boost::system;
using namespace boost::asio;
#define LOCALHOST_URL "http://localhost:9090"
namespace Slic3r {
namespace GUI {
class http_headers
{
std::string method;
std::string url;
std::string version;
std::map<std::string, std::string> headers;
public:
std::string get_response();
int content_length()
{
auto request = headers.find("content-length");
if (request != headers.end())
{
std::stringstream ssLength(request->second);
int content_length;
ssLength >> content_length;
return content_length;
}
return 0;
}
void on_read_header(std::string line)
{
//std::cout << "header: " << line << std::endl;
std::stringstream ssHeader(line);
std::string headerName;
std::getline(ssHeader, headerName, ':');
std::string value;
std::getline(ssHeader, value);
headers[headerName] = value;
}
void on_read_request_line(std::string line)
{
std::stringstream ssRequestLine(line);
ssRequestLine >> method;
ssRequestLine >> url;
ssRequestLine >> version;
std::cout << "request for resource: " << url << std::endl;
}
};
class session
{
asio::streambuf buff;
http_headers headers;
static void read_body(std::shared_ptr<session> pThis)
{
int nbuffer = 1000;
std::shared_ptr<std::vector<char>> bufptr = std::make_shared<std::vector<char>>(nbuffer);
asio::async_read(pThis->socket, boost::asio::buffer(*bufptr, nbuffer), [pThis](const boost::beast::error_code& e, std::size_t s)
{
});
}
static void read_next_line(std::shared_ptr<session> pThis)
{
asio::async_read_until(pThis->socket, pThis->buff, '\r', [pThis](const boost::beast::error_code& e, std::size_t s)
{
std::string line, ignore;
std::istream stream{ &pThis->buff };
std::getline(stream, line, '\r');
std::getline(stream, ignore, '\n');
pThis->headers.on_read_header(line);
if (line.length() == 0)
{
if (pThis->headers.content_length() == 0)
{
std::shared_ptr<std::string> str = std::make_shared<std::string>(pThis->headers.get_response());
asio::async_write(pThis->socket, boost::asio::buffer(str->c_str(), str->length()), [pThis, str](const boost::beast::error_code& e, std::size_t s)
{
std::cout << "done" << std::endl;
});
}
else
{
pThis->read_body(pThis);
}
}
else
{
pThis->read_next_line(pThis);
}
});
}
static void read_first_line(std::shared_ptr<session> pThis)
{
asio::async_read_until(pThis->socket, pThis->buff, '\r', [pThis](const boost::beast::error_code& e, std::size_t s)
{
std::string line, ignore;
std::istream stream{ &pThis->buff };
std::getline(stream, line, '\r');
std::getline(stream, ignore, '\n');
pThis->headers.on_read_request_line(line);
pThis->read_next_line(pThis);
});
}
public:
ip::tcp::socket socket;
session(io_service& io_service)
:socket(io_service)
{
}
static void interact(std::shared_ptr<session> pThis)
{
read_first_line(pThis);
}
};
class HttpServer {
public:
HttpServer();
boost::thread m_http_server_thread;
bool start_http_server = false;
bool is_started() { return start_http_server; }
void start();
void stop();
};
}
};
#endif

View file

@ -49,6 +49,8 @@ string &replace_str(string &str, const string &to_replaced, const string &newcha
return str;
}
int ZUserLogin::web_sequence_id = 20000;
ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_ANY, "BambuStudio")
{
SetBackgroundColour(*wxWHITE);
@ -262,6 +264,31 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
wxGetApp().handle_script_message(j.dump());
Close();
}
else if (strCmd == "get_localhost_url") {
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_localhost_url";
wxGetApp().start_http_server();
std::string sequence_id = j["sequence_id"].get<std::string>();
CallAfter([this, sequence_id] {
json ack_j;
ack_j["command"] = "get_localhost_url";
ack_j["response"]["base_url"] = LOCALHOST_URL;
ack_j["response"]["result"] = "success";
ack_j["sequence_id"] = sequence_id;
wxString str_js = wxString::Format("window.postMessage(%s)", ack_j.dump());
this->RunScript(str_js);
});
}
else if (strCmd == "thirdparty_login") {
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: thirdparty_login";
if (j["data"].contains("url")) {
std::string jump_url = j["data"]["url"].get<std::string>();
CallAfter([this, jump_url] {
wxString url = wxString::FromUTF8(jump_url);
wxLaunchDefaultBrowser(url);
});
}
return;
}
} catch (std::exception &e) {
wxMessageBox(e.what(), "parse json failed", wxICON_WARNING);
Close();
@ -274,8 +301,6 @@ void ZUserLogin::RunScript(const wxString &javascript)
// the "Run Script" dialog box, it is shown there for convenient updating.
m_javascript = javascript;
// wxLogMessage("Running JavaScript:\n%s\n", javascript);
if (!m_browser) return;
WebView::RunScript(m_browser, javascript);

View file

@ -27,8 +27,6 @@
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
class ZUserLogin : public wxDialog
@ -62,7 +60,7 @@ public:
bool run();
static int web_sequence_id;
private:
wxTimer *m_timer { nullptr };
void OnTimer(wxTimerEvent &event);

View file

@ -95,6 +95,7 @@ func_start_pubilsh NetworkAgent::start_publish_ptr = nullptr;
func_get_profile_3mf NetworkAgent::get_profile_3mf_ptr = nullptr;
func_get_model_publish_url NetworkAgent::get_model_publish_url_ptr = nullptr;
func_get_model_mall_home_url NetworkAgent::get_model_mall_home_url_ptr = nullptr;
func_get_my_profile NetworkAgent::get_my_profile_ptr = nullptr;
NetworkAgent::NetworkAgent()
@ -236,6 +237,7 @@ int NetworkAgent::initialize_network_module(bool using_backup)
get_profile_3mf_ptr = reinterpret_cast<func_get_profile_3mf>(get_network_function("bambu_network_get_profile_3mf"));
get_model_publish_url_ptr = reinterpret_cast<func_get_model_publish_url>(get_network_function("bambu_network_get_model_publish_url"));
get_model_mall_home_url_ptr = reinterpret_cast<func_get_model_mall_home_url>(get_network_function("bambu_network_get_model_mall_home_url"));
get_my_profile_ptr = reinterpret_cast<func_get_my_profile>(get_network_function("bambu_network_get_my_profile"));
return 0;
}
@ -331,6 +333,7 @@ int NetworkAgent::unload_network_module()
get_profile_3mf_ptr = nullptr;
get_model_publish_url_ptr = nullptr;
get_model_mall_home_url_ptr = nullptr;
get_my_profile_ptr = nullptr;
return 0;
}
@ -1103,4 +1106,15 @@ int NetworkAgent::get_model_mall_home_url(std::string* url)
return ret;
}
int NetworkAgent::get_my_profile(std::string token, unsigned int *http_code, std::string *http_body)
{
int ret = 0;
if (network_agent && get_my_profile_ptr) {
ret = get_my_profile_ptr(network_agent, token, http_code, http_body);
if (ret)
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("error network_agnet=%1%, ret = %2%") % network_agent % ret;
}
return ret;
}
} //namespace

View file

@ -75,6 +75,7 @@ typedef int (*func_start_pubilsh)(void *agent, PublishParams params, OnUpdateSta
typedef int (*func_get_profile_3mf)(void *agent, BBLProfile* profile);
typedef int (*func_get_model_publish_url)(void *agent, std::string* url);
typedef int (*func_get_model_mall_home_url)(void *agent, std::string* url);
typedef int (*func_get_my_profile)(void *agent, std::string token, unsigned int *http_code, std::string *http_body);
//the NetworkAgent class
@ -158,6 +159,7 @@ public:
int get_profile_3mf(BBLProfile* profile);
int get_model_publish_url(std::string* url);
int get_model_mall_home_url(std::string* url);
int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body);
private:
@ -231,6 +233,7 @@ private:
static func_get_profile_3mf get_profile_3mf_ptr;
static func_get_model_publish_url get_model_publish_url_ptr;
static func_get_model_mall_home_url get_model_mall_home_url_ptr;
static func_get_my_profile get_my_profile_ptr;
};
}