#pragma once #include #include #include #include #include #include #include namespace rublon { // Struktura do obsługi połączenia WebSocket class LWS { std::reference_wrapper< const Configuration > _config; const Configuration & conf() const noexcept { return _config.get(); } struct session_data { const Request & request; Response & response; int status{}; bool done{false}; }; static int callback(struct lws * wsi, enum lws_callback_reasons reason, void * user, void * in, size_t len) { auto * data = reinterpret_cast< session_data * >(user); int n; switch(reason) { /* because we are protocols[0] ... */ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: log(LogLevel::Error, "CLIENT_CONNECTION_ERROR: %s\n", in ? ( char * ) in : "(null)"); if(data) data->done = true; lws_cancel_service(lws_get_context(wsi)); return 0; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: if(data) data->done = true; lws_cancel_service(lws_get_context(wsi)); return 0; /* ...callbacks related to receiving the result... */ case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { data->status = lws_http_client_http_response(wsi); log(LogLevel::Info, "Connected with server response: %d\n", data->status); char tmp[512]; auto copy_header = [&](const auto name) { if (lws_hdr_custom_length(wsi, name.data(), name.size()) > 0 && lws_hdr_custom_copy(wsi, tmp, sizeof(tmp), name.data(), name.size()) > 0) { data->response.headers[{name.data(), name.size()-1}] = tmp; } }; using namespace std::string_view_literals; copy_header("x-ratelimit-limit:"sv); copy_header("x-ratelimit-remaining:"sv); copy_header("x-rublon-signature:"sv); break; } case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: { log(LogLevel::Info, "RECEIVE_CLIENT_HTTP_READ: read %d\n", ( int ) len); if(data && in && len) { data->response.body.append(static_cast< const char * >(in), len); } return 0; /* don't passthru */ } case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: { // Drain the socket; lws will subsequently trigger READ callbacks with 'in/len' char buf[LWS_PRE + 2048]; char * p = buf + LWS_PRE; int n = ( int ) sizeof(buf) - LWS_PRE; if(lws_http_client_read(wsi, &p, &n) < 0) return -1; return 0; /* don't passthru */ } case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: log(LogLevel::Info, "LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); if(data) data->done = true; lws_cancel_service(lws_get_context(wsi)); return 0; case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: { if(!lws_http_is_redirected_to_get(wsi)) { unsigned char ** pp = ( unsigned char ** ) in; unsigned char * end = (*pp) + len; // Add user headers (ensure names include ':' e.g. "Content-Type:") for(const auto & [name, value] : data->request.headers) { if(lws_add_http_header_by_name(wsi, ( const unsigned char * ) name.c_str(), ( const unsigned char * ) value.c_str(), ( int ) value.size(), pp, end)) { log(LogLevel::Error, "Header add error"); return -1; } } // Ensure Content-Length if sending a body if(!data->request.body.empty()) { std::string cl = std::to_string(data->request.body.size()); if(lws_add_http_header_by_name(wsi, ( const unsigned char * ) "Content-Length:", ( const unsigned char * ) cl.c_str(), ( int ) cl.size(), pp, end)) { log(LogLevel::Error, "Header Content-Length error"); return -1; } } // Tell lws the body will follow if(!data->request.body.empty()) lws_client_http_body_pending(wsi, 1); lws_callback_on_writable(wsi); } return 0; } case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: // according to doc, there is no need for LWS_PRE when using HTTP_WRITE n = lws_write(wsi, ( unsigned char * ) data->request.body.c_str(), data->request.body.size(), LWS_WRITE_HTTP_FINAL); if(n < 0) return -1; /* we only had one thing to send, so inform lws we are donefi * if we had more to send, call lws_callback_on_writable(wsi); * and just return 0 from callback. On having sent the last * part, call the below api instead.*/ lws_client_http_body_pending(wsi, 0); return 0; default: break; } return lws_callback_http_dummy(wsi, reason, user, in, len); }; static inline lws_protocols protocols[] = {{ "http-client", // const char *name LWS::callback, // lws_callback_function * 0, // size_t per_session_data_size 0, // size_t rx_buffer_size 0, // unsigned int id 0, // void *user 0 // size_t tx_packet_size }, {nullptr, nullptr, 0, 0, 0, nullptr, 0}}; public: lws_context_creation_info info{}; lws_context * _context{nullptr}; LWS(const Configuration & config) : _config{config} { std::memset(&info, 0, sizeof(info)); info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.protocols = protocols; _context = lws_create_context(&info); assert(_context != nullptr); auto lws_log_emit = [](int level, const char * line) { LogLevel rlevel{LLL_DEBUG}; if(level == LLL_ERR) rlevel = LogLevel::Error; if(level == LLL_WARN) rlevel = LogLevel::Warning; if(level == LLL_INFO) rlevel = LogLevel::Info; if(level == LLL_NOTICE) rlevel = LogLevel::Debug; if(level == LLL_DEBUG) rlevel = LogLevel::Debug; log(rlevel, "libwesockets: %s", line); }; // if(_config.get().logging) { lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG | LLL_USER | LLL_HEADER | LLL_CLIENT, lws_log_emit); // } else { // lws_set_log_level(LLL_ERR | LLL_WARN, lws_log_emit); // } } LWS(const LWS &) = delete; LWS(LWS &&) = delete; LWS & operator=(const LWS &) = delete; LWS & operator=(LWS &&) = delete; tl::expected< std::reference_wrapper< Response >, ConnectionError > request(std::string_view uri, const Request & request, Response & response) const { using namespace memory::literals; memory::Monotonic_8k_Resource memoryResource; // --- Parse URL --- std::pmr::string protocol = "https"; std::pmr::string host{&memoryResource}; std::pmr::string path{"/", &memoryResource}; std::string_view sv_uri(uri); if(auto pos = sv_uri.find("://"); pos != std::string_view::npos) { protocol = std::string(sv_uri.substr(0, pos)); sv_uri.remove_prefix(pos + 3); } auto slash_pos = sv_uri.find('/'); if(slash_pos != std::string_view::npos) { host = std::pmr::string(sv_uri.substr(0, slash_pos), &memoryResource); path = std::pmr::string(sv_uri.substr(slash_pos), &memoryResource); } else { host = std::pmr::string(sv_uri, &memoryResource); } const int use_ssl = (protocol == "https") ? LCCSCF_USE_SSL : 0; // --- Callback funkcji obsługującej klienta --- session_data client_data{request, response}; lws_client_connect_info ccinfo = {}; ccinfo.alpn = "http/1.1"; ccinfo.context = _context; ccinfo.address = host.c_str(); ccinfo.port = (protocol == "https") ? 443 : 80; ccinfo.path = path.c_str(); ccinfo.ssl_connection = use_ssl; ccinfo.host = host.c_str(); ccinfo.origin = host.c_str(); ccinfo.method = "POST"; ccinfo.protocol = protocols[0].name; ccinfo.userdata = &client_data; ccinfo.ietf_version_or_minus_one = -1; ccinfo.pwsi = nullptr; struct lws * wsi = lws_client_connect_via_info(&ccinfo); if(!wsi) { lws_context_destroy(_context); return tl::unexpected{ConnectionError{ConnectionError::Timeout, 0}}; } // --- Pętla serwisu --- while(lws_service(_context, 1000) >= 0) { if(!response.body.empty()) break; // odebrano dane } const auto http_code = client_data.status; if(http_code >= 500) { log(LogLevel::Error, "%s response with code %d ", "CURL", http_code); return tl::unexpected{ConnectionError{ConnectionError::HttpError, http_code}}; } for(const auto & [name, value] : response.headers) { log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str()); } log(LogLevel::Debug, "Body %s", response.body.c_str()); return response; } ~LWS() { if(_context) { lws_context_destroy(_context); _context = nullptr; } } }; } // namespace rublon