-
Notifications
You must be signed in to change notification settings - Fork 5
feat(http_client): move to opendht's builtin HTTP proxy #34
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
base: master
Are you sure you want to change the base?
Changes from all commits
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,7 +1,7 @@ | ||
| # This is the default dpaste configuration file. | ||
|
|
||
| ########################### | ||
| # OpenDHT's HTTP server # | ||
| ########################### | ||
| ################################### | ||
| # OpenDHT DhtProxyServer endpoint # | ||
| ################################### | ||
| host = 127.0.0.1 | ||
| port = 6509 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,4 +129,3 @@ class Bin { | |
| } /* dpaste */ | ||
|
|
||
| /* vim:set et sw=4 ts=4 tw=120: */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,10 @@ | |
| * along with dpaste. If not, see <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #include <fstream> | ||
| #include <algorithm> | ||
| #include <cstdint> | ||
| #include <list> | ||
| #include <sstream> | ||
|
|
||
| #include <curlpp/cURLpp.hpp> | ||
| #include <curlpp/Easy.hpp> | ||
|
|
@@ -28,69 +30,116 @@ | |
| #include <curlpp/Infos.hpp> | ||
| #include <nlohmann/json.hpp> | ||
| #include <b64/decode.h> | ||
| #include <b64/encode.h> | ||
|
|
||
| #include "http_client.h" | ||
| #include "log.h" | ||
| #include "node.h" | ||
|
|
||
| namespace dpaste { | ||
|
|
||
| using json = nlohmann::json; | ||
|
|
||
| static std::ofstream null("/dev/null"); | ||
| namespace { | ||
|
|
||
| std::string urlHost(const std::string& host) { | ||
| if (host.size() >= 2 && host.front() == '[' && host.back() == ']') | ||
| return host; | ||
| if (host.find(':') != std::string::npos) | ||
| return "[" + host + "]"; | ||
| return host; | ||
| } | ||
|
|
||
| } /* anonymous namespace */ | ||
|
|
||
| std::string HttpClient::get(const std::string& code) const { | ||
| try { | ||
| curlpp::Cleanup mycleanup; | ||
| curlpp::Easy req; | ||
| std::stringstream response; | ||
| req.setOpt<curlpp::options::Port>(port); | ||
| std::stringstream response, oss; | ||
| req.setOpt<curlpp::options::Url>(HTTP_PROTO+ | ||
| host+"/"+dht::InfoHash::get(code).toString() | ||
| +"?user_type="+dpaste::Node::DPASTE_USER_TYPE | ||
| ); | ||
| req.setOpt<curlpp::options::Url>(HTTP_PROTO + urlHost(host) + "/key/" + | ||
| dht::InfoHash::get(code).toString()); | ||
| req.setOpt(curlpp::Options::WriteStream(&response)); | ||
|
|
||
| try { | ||
| req.perform(); | ||
| /* server gives code 200 when everything is fine. */ | ||
| if (curlpp::Infos::ResponseCode::get(req) == 200) { | ||
| auto pr = json::parse(response.str()); | ||
| if (not pr.empty()) { | ||
| std::istringstream iss((*pr.begin())["base64"].dump()); | ||
| base64::decoder d; | ||
| d.decode(iss, oss); | ||
| /* DhtProxyServer returns one JSON Value per line; do not parse | ||
| * the whole response as a single JSON document. */ | ||
| std::istringstream lines(response.str()); | ||
| std::string line; | ||
| while (std::getline(lines, line)) { | ||
|
Owner
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. How to be sure that parsing line by line the output of the service will lead to valid JSON content? We can't know. The server's response should rather be parsed in its entirety.
Contributor
Author
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. Because we are reading NDJSON files. By definition, each line is a valid JSON document. |
||
| try { | ||
| const auto value = json::parse(line); | ||
| if (value.is_object() && | ||
| value.value("utype", std::string {}) == Node::DPASTE_USER_TYPE && | ||
| value.contains("data") && value["data"].is_string()) { | ||
| std::istringstream encoded(value["data"].get<std::string>()); | ||
| std::ostringstream decoded; | ||
| base64::decoder decoder; | ||
| decoder.decode(encoded, decoded); | ||
| return decoded.str(); | ||
| } | ||
| } catch (const std::exception&) { | ||
| /* Ignore malformed or incompatible Value objects. */ | ||
| } | ||
| } | ||
| } | ||
| } catch (curlpp::RuntimeError & e) { } | ||
| } catch (curlpp::RuntimeError & e) { | ||
| DPASTE_MSG("%s", e.what()); | ||
| } | ||
|
|
||
| return oss.str(); | ||
| } catch (curlpp::LogicError & e) { return {}; } | ||
| return {}; | ||
| } catch (curlpp::LogicError & e) { | ||
| DPASTE_MSG("%s", e.what()); | ||
| return {}; | ||
| } | ||
| } | ||
|
|
||
| bool HttpClient::put(const std::string& code, const std::string& data) const { | ||
| try { | ||
| curlpp::Cleanup mycleanup; | ||
| curlpp::Easy req; | ||
| req.setOpt<curlpp::options::Port>(port); | ||
| req.setOpt<curlpp::options::Url>(HTTP_PROTO+host+"/"+dht::InfoHash::get(code).toString()); | ||
| req.setOpt(curlpp::Options::WriteStream(&null)); | ||
| { | ||
| curlpp::Forms form_parts; | ||
| form_parts.push_back(new curlpp::FormParts::Content("user_type", dpaste::Node::DPASTE_USER_TYPE)); | ||
| form_parts.push_back(new curlpp::FormParts::Content("data", data)); | ||
| req.setOpt(new curlpp::options::HttpPost(form_parts)); | ||
| } | ||
| req.setOpt<curlpp::options::Url>(HTTP_PROTO + urlHost(host) + "/key/" + | ||
| dht::InfoHash::get(code).toString()); | ||
|
|
||
| std::istringstream input(data); | ||
| std::ostringstream encoded; | ||
| base64::encoder encoder; | ||
| encoder.encode(input, encoded); | ||
|
|
||
| /* libb64 wraps long output lines; the proxy expects one compact | ||
| * standard-base64 string in the JSON Value. */ | ||
| auto encoded_data = encoded.str(); | ||
| encoded_data.erase(std::remove_if(encoded_data.begin(), encoded_data.end(), | ||
| [](char c) { return c == '\r' || c == '\n'; }), encoded_data.end()); | ||
|
|
||
| const auto body = json { | ||
| {"data", encoded_data}, | ||
| {"utype", Node::DPASTE_USER_TYPE} | ||
| }.dump(); | ||
| req.setOpt(curlpp::Options::PostFields(body)); | ||
|
Owner
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. Why use
Contributor
Author
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. Proxy POST requires raw JSON PostFields, not multipart HttpPost. |
||
| req.setOpt(new curlpp::options::HttpHeader( | ||
| std::list<std::string> {"Content-Type: application/json"})); | ||
| std::stringstream response; | ||
| req.setOpt(curlpp::Options::WriteStream(&response)); | ||
|
|
||
| try { | ||
| req.perform(); | ||
| return curlpp::Infos::ResponseCode::get(req) == 200; | ||
| } catch (curlpp::RuntimeError & e) { | ||
| DPASTE_MSG("%s", e.what()); | ||
| return false; | ||
| } | ||
| } catch (curlpp::LogicError & e) { return false; } | ||
| } catch (curlpp::LogicError & e) { | ||
| DPASTE_MSG("%s", e.what()); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| } /* dpaste */ | ||
|
|
||
| /* vim:set et sw=4 ts=4 tw=120: */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| [Unit] | ||
| Description=OpenDHT HTTP proxy for dpaste | ||
| Wants=network-online.target | ||
| After=network-online.target | ||
|
|
||
| [Service] | ||
| Type=simple | ||
| CacheDirectory=dpaste | ||
| ExecStart=/usr/bin/dhtnode --service --port 0 --bootstrap bootstrap.jami.net:4222 --proxyserver 6509 --proxy-addr 127.0.0.1 --persist ${CACHE_DIRECTORY}/dhtnode | ||
| Restart=on-failure | ||
|
|
||
| [Install] | ||
| WantedBy=default.target |
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.
You removed the
user_typespec from the URL. That makes it so thegetoperation on the DHT node service's side won't have this spec and it will then pull more values than it should. This usertype spec is used in the DHT protocol to filter out values so requests are as light as possible and also per-application specific.I know that you are filtering values out afterwards, but at this point it's too late, network traffic was wasted for no reason.
Let's keep this network level optimization.
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.
The problem is the migration to OpenDHT’s
DhtProxyServer: its newGET /key/<hash>handler ignores query parameters entirely. Adding?user_type=dpasteback would look correct but provide no DHT-level filtering.