Skip to content

Commit 6054dd4

Browse files
committed
(http client) Add support for multipart HTTP POST upload + (ixsentry) Add support for uploading a minidump to sentry
1 parent 69aa383 commit 6054dd4

14 files changed

+302
-30
lines changed

DOCKER_VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7.3.5
1+
7.4.0

docs/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [7.4.0] - 2019-11-25
5+
6+
- (http client) Add support for multipart HTTP POST upload
7+
- (ixsentry) Add support for uploading a minidump to sentry
8+
49
## [7.3.5] - 2019-11-20
510

611
- On Darwin SSL, add ability to skip peer verification.

ixsentry/CMakeLists.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ add_library(ixsentry STATIC
1919
set(IXSENTRY_INCLUDE_DIRS
2020
.
2121
..
22-
../third_party
23-
../third_party/spdlog/include)
22+
../ixcore
23+
../third_party)
2424

2525
target_include_directories( ixsentry PUBLIC ${IXSENTRY_INCLUDE_DIRS} )

ixsentry/ixsentry/IXSentryClient.cpp

+50-23
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99
#include <chrono>
1010
#include <iostream>
11+
#include <fstream>
1112
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
12-
#include <spdlog/spdlog.h>
13+
#include <ixcore/utils/IXCoreLogger.h>
1314

1415

1516
namespace ix
@@ -18,6 +19,7 @@ namespace ix
1819
: _dsn(dsn)
1920
, _validDsn(false)
2021
, _luaFrameRegex("\t([^/]+):([0-9]+): in function ['<]([^/]+)['>]")
22+
, _httpClient(std::make_shared<HttpClient>(true))
2123
{
2224
const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)");
2325
std::smatch group;
@@ -169,39 +171,64 @@ namespace ix
169171

170172
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
171173
{
172-
auto args = _httpClient.createRequest();
174+
auto args = _httpClient->createRequest();
173175
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
174176
args->connectTimeout = 60;
175177
args->transferTimeout = 5 * 60;
176178
args->followRedirects = true;
177179
args->verbose = verbose;
178-
args->logger = [](const std::string& msg) { spdlog::info("request logger: {}", msg); };
180+
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
179181

180182
std::string body = computePayload(msg);
181-
HttpResponsePtr response = _httpClient.post(_url, body, args);
183+
HttpResponsePtr response = _httpClient->post(_url, body, args);
182184

183-
if (verbose)
184-
{
185-
for (auto it : response->headers)
186-
{
187-
spdlog::info("{}: {}", it.first, it.second);
188-
}
185+
return std::make_pair(response, body);
186+
}
189187

190-
spdlog::info("Upload size: {}", response->uploadSize);
191-
spdlog::info("Download size: {}", response->downloadSize);
188+
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
189+
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
190+
{
191+
std::stringstream ss;
192+
ss << "https://sentry.io/api/"
193+
<< project
194+
<< "/minidump?sentry_key="
195+
<< key;
192196

193-
spdlog::info("Status: {}", response->statusCode);
194-
if (response->errorCode != HttpErrorCode::Ok)
195-
{
196-
spdlog::info("error message: {}", response->errorMsg);
197-
}
197+
return ss.str();
198+
}
198199

199-
if (response->headers["Content-Type"] != "application/octet-stream")
200-
{
201-
spdlog::info("payload: {}", response->payload);
202-
}
203-
}
200+
//
201+
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
202+
//
203+
void SentryClient::uploadMinidump(
204+
const std::string& sentryMetadata,
205+
const std::string& minidumpBytes,
206+
const std::string& project,
207+
const std::string& key,
208+
bool verbose,
209+
const OnResponseCallback& onResponseCallback)
210+
{
211+
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
204212

205-
return std::make_pair(response, body);
213+
auto args = _httpClient->createRequest();
214+
args->verb = HttpClient::kPost;
215+
args->connectTimeout = 60;
216+
args->transferTimeout = 5 * 60;
217+
args->followRedirects = true;
218+
args->verbose = verbose;
219+
args->multipartBoundary = multipartBoundary;
220+
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
221+
222+
HttpFormDataParameters httpFormDataParameters;
223+
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
224+
225+
HttpParameters httpParameters;
226+
httpParameters["sentry"] = sentryMetadata;
227+
228+
args->url = computeUrl(project, key);
229+
args->body = _httpClient->serializeHttpFormDataParameters(multipartBoundary, httpFormDataParameters, httpParameters);
230+
231+
232+
_httpClient->performRequest(args, onResponseCallback);
206233
}
207234
} // namespace ix

ixsentry/ixsentry/IXSentryClient.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <ixwebsocket/IXHttpClient.h>
1111
#include <jsoncpp/json/json.h>
1212
#include <regex>
13+
#include <memory>
1314

1415
namespace ix
1516
{
@@ -23,12 +24,24 @@ namespace ix
2324

2425
Json::Value parseLuaStackTrace(const std::string& stack);
2526

27+
void uploadMinidump(
28+
const std::string& sentryMetadata,
29+
const std::string& minidumpBytes,
30+
const std::string& project,
31+
const std::string& key,
32+
bool verbose,
33+
const OnResponseCallback& onResponseCallback);
34+
2635
private:
2736
int64_t getTimestamp();
2837
std::string computeAuthHeader();
2938
std::string getIso8601();
3039
std::string computePayload(const Json::Value& msg);
3140

41+
std::string computeUrl(const std::string& project, const std::string& key);
42+
43+
void displayReponse(HttpResponsePtr response);
44+
3245
std::string _dsn;
3346
bool _validDsn;
3447
std::string _url;
@@ -41,7 +54,7 @@ namespace ix
4154

4255
std::regex _luaFrameRegex;
4356

44-
HttpClient _httpClient;
57+
std::shared_ptr<HttpClient> _httpClient;
4558
};
4659

4760
} // namespace ix

ixwebsocket/IXHttp.h

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ namespace ix
6666

6767
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
6868
using HttpParameters = std::map<std::string, std::string>;
69+
using HttpFormDataParameters = std::map<std::string, std::string>;
6970
using Logger = std::function<void(const std::string&)>;
7071
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
7172

@@ -75,6 +76,7 @@ namespace ix
7576
std::string verb;
7677
WebSocketHttpHeaders extraHeaders;
7778
std::string body;
79+
std::string multipartBoundary;
7880
int connectTimeout;
7981
int transferTimeout;
8082
bool followRedirects;

ixwebsocket/IXHttpClient.cpp

+70-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <assert.h>
1414
#include <cstring>
1515
#include <iomanip>
16+
#include <random>
1617
#include <sstream>
1718
#include <vector>
1819
#include <zlib.h>
@@ -198,8 +199,16 @@ namespace ix
198199
// Set default Content-Type if unspecified
199200
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
200201
{
201-
ss << "Content-Type: application/x-www-form-urlencoded"
202-
<< "\r\n";
202+
if (args->multipartBoundary.empty())
203+
{
204+
ss << "Content-Type: application/x-www-form-urlencoded"
205+
<< "\r\n";
206+
}
207+
else
208+
{
209+
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
210+
<< "\r\n";
211+
}
203212
}
204213
ss << "\r\n";
205214
ss << body;
@@ -597,6 +606,53 @@ namespace ix
597606
return ss.str();
598607
}
599608

609+
std::string HttpClient::serializeHttpFormDataParameters(
610+
const std::string& multipartBoundary,
611+
const HttpFormDataParameters& httpFormDataParameters,
612+
const HttpParameters& httpParameters)
613+
{
614+
//
615+
// --AaB03x
616+
// Content-Disposition: form-data; name="submit-name"
617+
618+
// Larry
619+
// --AaB03x
620+
// Content-Disposition: form-data; name="foo.txt"; filename="file1.txt"
621+
// Content-Type: text/plain
622+
623+
// ... contents of file1.txt ...
624+
// --AaB03x--
625+
//
626+
std::stringstream ss;
627+
628+
for (auto&& it : httpFormDataParameters)
629+
{
630+
ss << "--" << multipartBoundary << "\r\n"
631+
<< "Content-Disposition:"
632+
<< " form-data; name=\"" << it.first << "\";"
633+
<< " filename=\"" << it.first << "\""
634+
<< "\r\n"
635+
<< "Content-Type: application/octet-stream"
636+
<< "\r\n"
637+
<< "\r\n"
638+
<< it.second << "\r\n";
639+
}
640+
641+
for (auto&& it : httpParameters)
642+
{
643+
ss << "--" << multipartBoundary << "\r\n"
644+
<< "Content-Disposition:"
645+
<< " form-data; name=\"" << it.first << "\";"
646+
<< "\r\n"
647+
<< "\r\n"
648+
<< it.second << "\r\n";
649+
}
650+
651+
ss << "--" << multipartBoundary << "\r\n";
652+
653+
return ss.str();
654+
}
655+
600656
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
601657
{
602658
z_stream inflateState;
@@ -649,4 +705,16 @@ namespace ix
649705
args->logger(msg);
650706
}
651707
}
708+
709+
std::string HttpClient::generateMultipartBoundary()
710+
{
711+
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
712+
713+
static std::random_device rd;
714+
static std::mt19937 generator(rd());
715+
716+
std::shuffle(str.begin(), str.end(), generator);
717+
718+
return str;
719+
}
652720
} // namespace ix

ixwebsocket/IXHttpClient.h

+7
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ namespace ix
6464

6565
std::string serializeHttpParameters(const HttpParameters& httpParameters);
6666

67+
std::string serializeHttpFormDataParameters(
68+
const std::string& multipartBoundary,
69+
const HttpFormDataParameters& httpFormDataParameters,
70+
const HttpParameters& httpParameters = HttpParameters());
71+
72+
std::string generateMultipartBoundary();
73+
6774
std::string urlEncode(const std::string& value);
6875

6976
const static std::string kPost;

ixwebsocket/IXWebSocketVersion.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77
#pragma once
88

9-
#define IX_WEBSOCKET_VERSION "7.3.5"
9+
#define IX_WEBSOCKET_VERSION "7.4.0"

ws/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ add_executable(ws
5656
ws_httpd.cpp
5757
ws_autobahn.cpp
5858
ws_proxy_server.cpp
59+
ws_sentry_minidump_upload.cpp
5960
ws.cpp)
6061

6162
target_link_libraries(ws ixsnake)

ws/ws.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ int main(int argc, char** argv)
7373
std::string appsConfigPath("appsConfig.json");
7474
std::string subprotocol;
7575
std::string remoteHost;
76+
std::string minidump;
77+
std::string metadata;
78+
std::string project;
79+
std::string key;
7680
ix::SocketTLSOptions tlsOptions;
7781
std::string ciphers;
7882
std::string redirectUrl;
@@ -311,6 +315,13 @@ int main(int argc, char** argv)
311315
proxyServerApp->add_option("--remote_host", remoteHost, "Remote Hostname");
312316
proxyServerApp->add_flag("-v", verbose, "Verbose");
313317

318+
CLI::App* minidumpApp = app.add_subcommand("upload_minidump", "Upload a minidump to sentry");
319+
minidumpApp->add_option("--minidump", minidump, "Minidump path")->check(CLI::ExistingPath);
320+
minidumpApp->add_option("--metadata", metadata, "Hostname")->check(CLI::ExistingPath);
321+
minidumpApp->add_option("--project", project, "Sentry Project")->required();
322+
minidumpApp->add_option("--key", key, "Sentry Key")->required();
323+
minidumpApp->add_flag("-v", verbose, "Verbose");
324+
314325
CLI11_PARSE(app, argc, argv);
315326

316327
// pid file handling
@@ -453,6 +464,10 @@ int main(int argc, char** argv)
453464
{
454465
ret = ix::ws_proxy_server_main(port, hostname, tlsOptions, remoteHost, verbose);
455466
}
467+
else if (app.got_subcommand("upload_minidump"))
468+
{
469+
ret = ix::ws_sentry_minidump_upload(metadata, minidump, project, key, verbose);
470+
}
456471
else if (version)
457472
{
458473
std::cout << "ws " << ix::userAgent() << std::endl;

ws/ws.h

+6
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,10 @@ namespace ix
148148
const ix::SocketTLSOptions& tlsOptions,
149149
const std::string& remoteHost,
150150
bool verbose);
151+
152+
int ws_sentry_minidump_upload(const std::string& metadataPath,
153+
const std::string& minidump,
154+
const std::string& project,
155+
const std::string& key,
156+
bool verbose);
151157
} // namespace ix

ws/ws_cobra_to_sentry.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ namespace ix
8181

8282
auto ret = sentryClient.send(msg, verbose);
8383
HttpResponsePtr response = ret.first;
84+
85+
if (verbose)
86+
{
87+
for (auto it : response->headers)
88+
{
89+
spdlog::info("{}: {}", it.first, it.second);
90+
}
91+
92+
spdlog::info("Upload size: {}", response->uploadSize);
93+
spdlog::info("Download size: {}", response->downloadSize);
94+
95+
spdlog::info("Status: {}", response->statusCode);
96+
if (response->errorCode != HttpErrorCode::Ok)
97+
{
98+
spdlog::info("error message: {}", response->errorMsg);
99+
}
100+
101+
if (response->headers["Content-Type"] != "application/octet-stream")
102+
{
103+
spdlog::info("payload: {}", response->payload);
104+
}
105+
}
106+
84107
if (response->statusCode != 200)
85108
{
86109
spdlog::error("Error sending data to sentry: {}", response->statusCode);

0 commit comments

Comments
 (0)