From 3e979843e480b3e5971780b3454ab38fd0937cb6 Mon Sep 17 00:00:00 2001 From: Alex Zolotarev Date: Fri, 10 Apr 2015 00:00:09 +0300 Subject: [PATCH] [alohalytics] HTTP PUT/DELETE and basic auth support in HttpClient. --- 3party/Alohalytics/alohalytics.pro | 8 ++ .../Alohalytics/examples/android/build.gradle | 1 + .../alohalytics/test/HttpTransportTest.java | 81 +++++++++++-------- .../java/org/alohalytics/HttpTransport.java | 48 ++++++++--- .../src/android/jni/jni_alohalytics.cc | 48 +++++++++-- .../src/apple/http_client_apple.mm | 25 +++--- 3party/Alohalytics/src/cpp/alohalytics.cc | 2 +- 3party/Alohalytics/src/http_client.h | 53 ++++++++---- .../Alohalytics/src/posix/http_client_curl.cc | 17 ++-- 9 files changed, 197 insertions(+), 86 deletions(-) diff --git a/3party/Alohalytics/alohalytics.pro b/3party/Alohalytics/alohalytics.pro index a83f215bff..54bfff2f2e 100644 --- a/3party/Alohalytics/alohalytics.pro +++ b/3party/Alohalytics/alohalytics.pro @@ -22,3 +22,11 @@ macx-* { QMAKE_OBJECTIVE_CFLAGS *= -fobjc-arc QMAKE_LFLAGS *= -framework Foundation } + +linux-* { + SOURCES += src/posix/http_client_curl.cc +} + +android-* { + SOURCES += src/android/jni/jni_alohalytics.cc +} diff --git a/3party/Alohalytics/examples/android/build.gradle b/3party/Alohalytics/examples/android/build.gradle index 884aaf38c1..271bbab7ff 100644 --- a/3party/Alohalytics/examples/android/build.gradle +++ b/3party/Alohalytics/examples/android/build.gradle @@ -18,6 +18,7 @@ apply plugin: 'com.android.application' dependencies { // This one is needed to get Google Play advertising ID if Google Play Services are available on the device. compile 'com.google.android.gms:play-services-base:+' + compile 'com.google.android.gms:play-services-ads:+' } android { diff --git a/3party/Alohalytics/examples/android/src/androidTest/java/org/alohalytics/test/HttpTransportTest.java b/3party/Alohalytics/examples/android/src/androidTest/java/org/alohalytics/test/HttpTransportTest.java index e5b13218ba..11ee88b8c7 100644 --- a/3party/Alohalytics/examples/android/src/androidTest/java/org/alohalytics/test/HttpTransportTest.java +++ b/3party/Alohalytics/examples/android/src/androidTest/java/org/alohalytics/test/HttpTransportTest.java @@ -40,6 +40,7 @@ public class HttpTransportTest extends InstrumentationTestCase { } public static final String CACHE_DIR = "/data/data/org.alohalytics.demoapp/cache/"; + public static final String HTTPBIN_POST_URL = "http://httpbin.org/post"; @Override protected void setUp() { @@ -72,10 +73,9 @@ public class HttpTransportTest extends InstrumentationTestCase { } public void testPostFromMemoryIntoMemory() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post"); final String postBody = "Hello, World!"; - p.data = postBody.getBytes(); - p.contentType = "application/octet-stream"; + final HttpTransport.Params p = new HttpTransport.Params(HTTPBIN_POST_URL); + p.setData(postBody.getBytes(), "application/octet-stream", "POST"); final HttpTransport.Params r = HttpTransport.run(p); assertEquals(200, r.httpResponseCode); final String receivedBody = new String(r.data); @@ -83,10 +83,21 @@ public class HttpTransportTest extends InstrumentationTestCase { assertTrue(receivedBody, receivedBody.contains(postBody)); } + public void testPutFromMemoryIntoMemory() throws Exception { + final String putBody = "This is HTTP PUT request"; + final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/put"); + p.setData(putBody.getBytes(), "application/octet-stream", "PUT"); + final HttpTransport.Params r = HttpTransport.run(p); + assertEquals(200, r.httpResponseCode); + final String receivedBody = new String(r.data); + // Server mirrors our content. + assertTrue(receivedBody, receivedBody.contains(putBody)); + } + public void testPostMissingContentType() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post"); - p.data = "Hello, World!".getBytes(); // here is missing p.contentType = "application/octet-stream"; + final HttpTransport.Params p = new HttpTransport.Params(HTTPBIN_POST_URL); + p.setData("Hello, World!".getBytes(), null, "POST"); boolean caughtException = false; try { final HttpTransport.Params r = HttpTransport.run(p); @@ -98,9 +109,8 @@ public class HttpTransportTest extends InstrumentationTestCase { } public void testPostFromInvalidFile() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post"); - p.inputFilePath = getFullWritablePathForFile("this_file_should_not_exist"); - p.contentType = "text/plain"; + final HttpTransport.Params p = new HttpTransport.Params(HTTPBIN_POST_URL); + p.setInputFilePath(getFullWritablePathForFile("this_file_should_not_exist"), "text/plain", "POST"); boolean caughtException = false; try { final HttpTransport.Params r = HttpTransport.run(p); @@ -112,9 +122,8 @@ public class HttpTransportTest extends InstrumentationTestCase { } public void testPostFromFileIntoMemory() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post"); - p.inputFilePath = getFullWritablePathForFile("some_input_test_file_for_http_post"); - p.contentType = "text/plain"; + final HttpTransport.Params p = new HttpTransport.Params(HTTPBIN_POST_URL); + p.setInputFilePath(getFullWritablePathForFile("some_input_test_file_for_http_post"), "text/plain", "POST"); try { // Use file name as a test string for the post body. Util.WriteStringToFile(p.inputFilePath, p.inputFilePath); @@ -128,27 +137,25 @@ public class HttpTransportTest extends InstrumentationTestCase { } public void testPostFromMemoryIntoFile() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post"); + final HttpTransport.Params p = new HttpTransport.Params(HTTPBIN_POST_URL); + p.setData(HTTPBIN_POST_URL.getBytes(), "text/plain", "POST"); p.outputFilePath = getFullWritablePathForFile("some_output_test_file_for_http_post"); - p.data = p.url.getBytes(); // Use server url as a test string for the post body. - p.contentType = "text/plain"; try { final HttpTransport.Params r = HttpTransport.run(p); assertEquals(200, r.httpResponseCode); // TODO(AlexZ): Think about using data field in the future for error pages (404 etc) //assertNull(r.data); final String receivedBody = Util.ReadFileAsUtf8String(p.outputFilePath); - assertTrue(receivedBody, receivedBody.contains("\"data\": \"http://httpbin.org/post\"")); + assertTrue(receivedBody, receivedBody.contains("\"data\": \"" + HTTPBIN_POST_URL + "\"")); } finally { (new File(p.outputFilePath)).delete(); } } public void testPostFromFileIntoFile() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post"); - p.inputFilePath = getFullWritablePathForFile("some_complex_input_test_file_for_http_post"); + final HttpTransport.Params p = new HttpTransport.Params(HTTPBIN_POST_URL); + p.setInputFilePath(getFullWritablePathForFile("some_complex_input_test_file_for_http_post"), "text/plain", "POST"); p.outputFilePath = getFullWritablePathForFile("some_complex_output_test_file_for_http_post"); - p.contentType = "text/plain"; final String postBodyToSend = "Aloha, this text should pass from one file to another. Mahalo!"; try { Util.WriteStringToFile(postBodyToSend, p.inputFilePath); @@ -178,10 +185,10 @@ public class HttpTransportTest extends InstrumentationTestCase { } public void testHttpRedirect302() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/redirect-to?url=/get"); + final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/redirect-to?url=http%3A%2F%2Falohalytics.org%2F"); final HttpTransport.Params r = HttpTransport.run(p); - assertEquals(200, r.httpResponseCode); - assertEquals(r.receivedUrl, "http://httpbin.org/get"); + assertEquals(302, r.httpResponseCode); + assertEquals(r.receivedUrl, "http://alohalytics.org/"); assertTrue(!r.url.equals(r.receivedUrl)); } @@ -194,20 +201,12 @@ public class HttpTransportTest extends InstrumentationTestCase { assertTrue(receivedBody.contains(p.userAgent)); } - // Default HTTPUrlConnection implementation doesn't automatically follow http <==> https redirects - public void disabled_testHttpRedirect301FromHttpToHttps() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://github.com"); - final HttpTransport.Params r = HttpTransport.run(p); - assertEquals(200, r.httpResponseCode); - assertEquals(r.receivedUrl, "https://github.com/"); - assertTrue(r.url.equals(r.receivedUrl)); - } - public void testHttpRedirect301() throws Exception { final HttpTransport.Params p = new HttpTransport.Params("http://maps.me"); final HttpTransport.Params r = HttpTransport.run(p); - assertEquals(200, r.httpResponseCode); - assertEquals(r.receivedUrl, "http://maps.me/en/home"); + // Client should not follow redirects automatically. + assertEquals(301, r.httpResponseCode); + assertEquals(r.receivedUrl, "http://maps.me/en/"); assertTrue(!r.url.equals(r.receivedUrl)); } @@ -225,9 +224,8 @@ public class HttpTransportTest extends InstrumentationTestCase { } public void testPostFromEmptyFileIntoMemory() throws Exception { - final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post"); - p.inputFilePath = getFullWritablePathForFile("empty_input_test_file_for_http_post"); - p.contentType = "text/plain"; + final HttpTransport.Params p = new HttpTransport.Params(HTTPBIN_POST_URL); + p.setInputFilePath(getFullWritablePathForFile("empty_input_test_file_for_http_post"), "text/plain", "POST"); try { Util.WriteStringToFile("", p.inputFilePath); final HttpTransport.Params r = HttpTransport.run(p); @@ -248,4 +246,17 @@ public class HttpTransportTest extends InstrumentationTestCase { assertEquals("application/json", r.contentType); } + public void testHttpBasicAuth() throws Exception { + final String user = "user"; + final String password = "password"; + final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/basic-auth/" + user + "/" + password); + p.basicAuthUser = user; + p.basicAuthPassword = password; + final HttpTransport.Params r = HttpTransport.run(p); + assertEquals(200, r.httpResponseCode); + assertEquals(p.url, r.receivedUrl); + final String receivedBody = new String(r.data); + assertTrue(receivedBody, receivedBody.contains("\"authenticated\": true")); + } + } diff --git a/3party/Alohalytics/src/android/java/org/alohalytics/HttpTransport.java b/3party/Alohalytics/src/android/java/org/alohalytics/HttpTransport.java index c05792901b..5d34dbcd41 100644 --- a/3party/Alohalytics/src/android/java/org/alohalytics/HttpTransport.java +++ b/3party/Alohalytics/src/android/java/org/alohalytics/HttpTransport.java @@ -24,6 +24,7 @@ package org.alohalytics; +import android.util.Base64; import android.util.Log; import java.io.BufferedInputStream; @@ -46,6 +47,9 @@ public class HttpTransport { public static int TIMEOUT_IN_MILLISECONDS = 30000; public static Params run(final Params p) throws IOException, NullPointerException { + if (p.httpMethod == null) { + throw new NullPointerException("Please set valid HTTP method for request at Params.httpMethod field."); + } HttpURLConnection connection = null; if (p.debugMode) Log.d(TAG, "Connecting to " + p.url); @@ -56,13 +60,18 @@ public class HttpTransport { connection.setConnectTimeout(TIMEOUT_IN_MILLISECONDS); connection.setReadTimeout(TIMEOUT_IN_MILLISECONDS); connection.setUseCaches(false); + connection.setRequestMethod(p.httpMethod); + if (p.basicAuthUser != null) { + final String encoded = Base64.encodeToString((p.basicAuthUser + ":" + p.basicAuthPassword).getBytes(), Base64.NO_WRAP); + connection.setRequestProperty("Authorization", "Basic " + encoded); + } if (p.userAgent != null) { connection.setRequestProperty("User-Agent", p.userAgent); } if (p.inputFilePath != null || p.data != null) { - // POST data to the server. + // Send (POST, PUT...) data to the server. if (p.contentType == null) { - throw new NullPointerException("Please set Content-Type for POST requests."); + throw new NullPointerException("Please set Content-Type for request."); } connection.setRequestProperty("Content-Type", p.contentType); if (p.contentEncoding != null) { @@ -78,7 +87,7 @@ public class HttpTransport { os.close(); } if (p.debugMode) - Log.d(TAG, "Sent POST with content of size " + p.data.length); + Log.d(TAG, "Sent " + p.httpMethod + " with content of size " + p.data.length); } else { final File file = new File(p.inputFilePath); assert (file.length() == (int) file.length()); @@ -93,14 +102,18 @@ public class HttpTransport { istream.close(); // IOException ostream.close(); // IOException if (p.debugMode) - Log.d(TAG, "Sent POST with file of size " + file.length()); + Log.d(TAG, "Sent " + p.httpMethod + " with file of size " + file.length()); } } - // GET data from the server or receive POST response body + // GET data from the server or receive response body p.httpResponseCode = connection.getResponseCode(); if (p.debugMode) Log.d(TAG, "Received HTTP " + p.httpResponseCode + " from server."); - p.receivedUrl = connection.getURL().toString(); + if (p.httpResponseCode >= 300 && p.httpResponseCode < 400) { + p.receivedUrl = connection.getHeaderField("Location"); + } else { + p.receivedUrl = connection.getURL().toString(); + } p.contentType = connection.getContentType(); p.contentEncoding = connection.getContentEncoding(); // This implementation receives any data only if we have HTTP::OK (200). @@ -150,14 +163,13 @@ public class HttpTransport { public String url = null; // Can be different from url in case of redirects. public String receivedUrl = null; - // SHOULD be specified for any POST request (any request where we send data to the server). + public String httpMethod = null; + // Should be specified for any request whose method allows non-empty body. // On return, contains received Content-Type or null. public String contentType = null; - // Can be specified for any POST request (any request where we send data to the server). + // Can be specified for any request whose method allows non-empty body. // On return, contains received Content-Encoding or null. public String contentEncoding = null; - // GET if null and inputFilePath is null. - // Sent in POST otherwise. public byte[] data = null; // Send from input file if specified instead of data. public String inputFilePath = null; @@ -165,11 +177,27 @@ public class HttpTransport { public String outputFilePath = null; // Optionally client can override default HTTP User-Agent. public String userAgent = null; + public String basicAuthUser = null; + public String basicAuthPassword = null; public int httpResponseCode = -1; public boolean debugMode = false; + // Simple GET request constructor. public Params(String url) { this.url = url; + httpMethod = "GET"; + } + + public void setData(byte[] data, String contentType, String httpMethod) { + this.data = data; + this.contentType = contentType; + this.httpMethod = httpMethod; + } + + public void setInputFilePath(String path, String contentType, String httpMethod) { + this.inputFilePath = path; + this.contentType = contentType; + this.httpMethod = httpMethod; } } diff --git a/3party/Alohalytics/src/android/jni/jni_alohalytics.cc b/3party/Alohalytics/src/android/jni/jni_alohalytics.cc index 87063478c9..98115e87e4 100644 --- a/3party/Alohalytics/src/android/jni/jni_alohalytics.cc +++ b/3party/Alohalytics/src/android/jni/jni_alohalytics.cc @@ -25,6 +25,7 @@ SOFTWARE. #include #include #include +#include #include "../../alohalytics.h" #include "../../http_client.h" @@ -245,18 +246,29 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { // Cache it on the first call. const static jfieldID dataField = env->GetFieldID(g_httpParamsClass, "data", "[B"); - if (!post_body_.empty()) { - const auto jniPostData = MakePointerScopeGuard(env->NewByteArray(post_body_.size()), deleteLocalRef); + if (!body_data_.empty()) { + const auto jniPostData = MakePointerScopeGuard(env->NewByteArray(body_data_.size()), deleteLocalRef); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION - env->SetByteArrayRegion(jniPostData.get(), 0, post_body_.size(), - reinterpret_cast(post_body_.data())); + env->SetByteArrayRegion(jniPostData.get(), 0, body_data_.size(), + reinterpret_cast(body_data_.data())); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION env->SetObjectField(httpParamsObject.get(), dataField, jniPostData.get()); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION } + assert(http_method_.empty()); + const static jfieldID httpMethodField = + env->GetFieldID(g_httpParamsClass, "httpMethod", "Ljava/lang/String;"); + { + const auto jniHttpMethod = MakePointerScopeGuard(env->NewStringUTF(http_method_.c_str()), deleteLocalRef); + CLEAR_AND_RETURN_FALSE_ON_EXCEPTION + + env->SetObjectField(httpParamsObject.get(), httpMethodField, jniHttpMethod.get()); + CLEAR_AND_RETURN_FALSE_ON_EXCEPTION + } + const static jfieldID contentTypeField = env->GetFieldID(g_httpParamsClass, "contentType", "Ljava/lang/String;"); if (!content_type_.empty()) { @@ -289,11 +301,11 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { CLEAR_AND_RETURN_FALSE_ON_EXCEPTION } - if (!post_file_.empty()) { + if (!body_file_.empty()) { const static jfieldID inputFilePathField = env->GetFieldID(g_httpParamsClass, "inputFilePath", "Ljava/lang/String;"); - const auto jniInputFilePath = MakePointerScopeGuard(env->NewStringUTF(post_file_.c_str()), deleteLocalRef); + const auto jniInputFilePath = MakePointerScopeGuard(env->NewStringUTF(body_file_.c_str()), deleteLocalRef); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION env->SetObjectField(httpParamsObject.get(), inputFilePathField, jniInputFilePath.get()); @@ -312,6 +324,30 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { CLEAR_AND_RETURN_FALSE_ON_EXCEPTION } + if (!basic_auth_user_.empty()) { + const static jfieldID basicAuthUserField = + env->GetFieldID(g_httpParamsClass, "basicAuthUser", "Ljava/lang/String;"); + + const auto jniBasicAuthUser = + MakePointerScopeGuard(env->NewStringUTF(basic_auth_user_.c_str()), deleteLocalRef); + CLEAR_AND_RETURN_FALSE_ON_EXCEPTION + + env->SetObjectField(httpParamsObject.get(), basicAuthUserField, jniBasicAuthUser.get()); + CLEAR_AND_RETURN_FALSE_ON_EXCEPTION + } + + if (!basic_auth_password_.empty()) { + const static jfieldID basicAuthPasswordField = + env->GetFieldID(g_httpParamsClass, "basicAuthPassword", "Ljava/lang/String;"); + + const auto jniBasicAuthPassword = + MakePointerScopeGuard(env->NewStringUTF(basic_auth_password_.c_str()), deleteLocalRef); + CLEAR_AND_RETURN_FALSE_ON_EXCEPTION + + env->SetObjectField(httpParamsObject.get(), basicAuthPasswordField, jniBasicAuthPassword.get()); + CLEAR_AND_RETURN_FALSE_ON_EXCEPTION + } + const static jfieldID debugModeField = env->GetFieldID(g_httpParamsClass, "debugMode", "Z"); env->SetBooleanField(httpParamsObject.get(), debugModeField, debug_mode_); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION diff --git a/3party/Alohalytics/src/apple/http_client_apple.mm b/3party/Alohalytics/src/apple/http_client_apple.mm index 93f1092de2..81e9389ddc 100644 --- a/3party/Alohalytics/src/apple/http_client_apple.mm +++ b/3party/Alohalytics/src/apple/http_client_apple.mm @@ -50,19 +50,25 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { [NSURL URLWithString:[NSString stringWithUTF8String:url_requested_.c_str()]] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:TIMEOUT_IN_SECONDS]; - if (!content_type_.empty()) + request.HTTPMethod = [NSString stringWithUTF8String:http_method_.c_str()]; + if (!content_type_.empty()) { [request setValue:[NSString stringWithUTF8String:content_type_.c_str()] forHTTPHeaderField:@"Content-Type"]; - if (!content_encoding_.empty()) + } + if (!content_encoding_.empty()) { [request setValue:[NSString stringWithUTF8String:content_encoding_.c_str()] forHTTPHeaderField:@"Content-Encoding"]; - if (!user_agent_.empty()) + } + if (!user_agent_.empty()) { [request setValue:[NSString stringWithUTF8String:user_agent_.c_str()] forHTTPHeaderField:@"User-Agent"]; - - if (!post_body_.empty()) { - request.HTTPBody = [NSData dataWithBytes:post_body_.data() length:post_body_.size()]; - request.HTTPMethod = @"POST"; - } else if (!post_file_.empty()) { + } + if (!basic_auth_user_.empty()) { + NSData * loginAndPassword = [[NSString stringWithUTF8String:(basic_auth_user_ + ":" + basic_auth_password_).c_str()] dataUsingEncoding:NSUTF8StringEncoding]; + [request setValue:[NSString stringWithFormat:@"Basic %@", [loginAndPassword base64Encoding]] forHTTPHeaderField:@"Authorization"]; + } + if (!body_data_.empty()) { + request.HTTPBody = [NSData dataWithBytes:body_data_.data() length:body_data_.size()]; + } else if (!body_file_.empty()) { NSError * err = nil; - NSString * path = [NSString stringWithUTF8String:post_file_.c_str()]; + NSString * path = [NSString stringWithUTF8String:body_file_.c_str()]; const unsigned long long file_size = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err].fileSize; if (err) { error_code_ = static_cast(err.code); @@ -72,7 +78,6 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { return false; } request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:path]; - request.HTTPMethod = @"POST"; [request setValue:[NSString stringWithFormat:@"%llu", file_size] forHTTPHeaderField:@"Content-Length"]; } diff --git a/3party/Alohalytics/src/cpp/alohalytics.cc b/3party/Alohalytics/src/cpp/alohalytics.cc index f2c781ffa7..426fdffc2c 100644 --- a/3party/Alohalytics/src/cpp/alohalytics.cc +++ b/3party/Alohalytics/src/cpp/alohalytics.cc @@ -60,7 +60,7 @@ bool Stats::UploadBuffer(const std::string& url, std::string&& buffer, bool debu try { // TODO(AlexZ): Refactor FileStorageQueue to automatically append ID and gzip files, so we don't need // temporary memory buffer any more and files take less space. - request.set_post_body(alohalytics::Gzip(buffer), "application/alohalytics-binary-blob", "gzip"); + request.set_body_data(alohalytics::Gzip(buffer), "application/alohalytics-binary-blob", "POST", "gzip"); return request.RunHTTPRequest() && 200 == request.error_code() && !request.was_redirected(); } catch (const std::exception& ex) { if (debug_mode) { diff --git a/3party/Alohalytics/src/http_client.h b/3party/Alohalytics/src/http_client.h index 6096c800a1..dead7377df 100644 --- a/3party/Alohalytics/src/http_client.h +++ b/3party/Alohalytics/src/http_client.h @@ -26,7 +26,6 @@ SOFTWARE. #define HTTP_CLIENT_H #include -#include namespace alohalytics { @@ -41,7 +40,7 @@ class HTTPClientPlatformWrapper { // Contains final content's url taking redirects (if any) into an account. std::string url_received_; int error_code_ = kNotInitialized; - std::string post_file_; + std::string body_file_; // Used instead of server_reply_ if set. std::string received_file_; // Data we received from the server if output_file_ wasn't initialized. @@ -51,7 +50,10 @@ class HTTPClientPlatformWrapper { std::string content_encoding_; std::string content_encoding_received_; std::string user_agent_; - std::string post_body_; + std::string body_data_; + std::string http_method_ = "GET"; + std::string basic_auth_user_; + std::string basic_auth_password_; bool debug_mode_ = false; HTTPClientPlatformWrapper(const HTTPClientPlatformWrapper&) = delete; @@ -69,12 +71,20 @@ class HTTPClientPlatformWrapper { url_requested_ = url; return *this; } - // This method is mutually exclusive with set_post_body(). - HTTPClientPlatformWrapper& set_post_file(const std::string& post_file, const std::string& content_type) { - post_file_ = post_file; + HTTPClientPlatformWrapper& set_http_method(const std::string& method) { + http_method_ = method; + return *this; + } + // This method is mutually exclusive with set_body_data(). + HTTPClientPlatformWrapper& set_body_file(const std::string& body_file, + const std::string& content_type, + const std::string& http_method = "POST", + const std::string& content_encoding = "") { + body_file_ = body_file; + body_data_.clear(); content_type_ = content_type; - // TODO (dkorolev) replace with exceptions as discussed offline. - assert(post_body_.empty()); + http_method_ = http_method; + content_encoding_ = content_encoding; return *this; } // If set, stores server reply in file specified. @@ -86,28 +96,37 @@ class HTTPClientPlatformWrapper { user_agent_ = user_agent; return *this; } - // This method is mutually exclusive with set_post_file(). - HTTPClientPlatformWrapper& set_post_body(const std::string& post_body, + // This method is mutually exclusive with set_body_file(). + HTTPClientPlatformWrapper& set_body_data(const std::string& body_data, const std::string& content_type, + const std::string& http_method = "POST", const std::string& content_encoding = "") { - post_body_ = post_body; + body_data_ = body_data; + body_file_.clear(); content_type_ = content_type; + http_method_ = http_method; content_encoding_ = content_encoding; - // TODO (dkorolev) replace with exceptions as discussed offline. - assert(post_file_.empty()); return *this; } // Move version to avoid string copying. - // This method is mutually exclusive with set_post_file(). - HTTPClientPlatformWrapper& set_post_body(std::string&& post_body, + // This method is mutually exclusive with set_body_file(). + HTTPClientPlatformWrapper& set_body_data(std::string&& body_data, const std::string& content_type, + const std::string& http_method = "POST", const std::string& content_encoding = "") { - post_body_ = std::move(post_body); - post_file_.clear(); + body_data_ = std::move(body_data); + body_file_.clear(); content_type_ = content_type; + http_method_ = http_method; content_encoding_ = content_encoding; return *this; } + // HTTP Basic Auth. + HTTPClientPlatformWrapper& set_user_and_password(const std::string& user, const std::string& password) { + basic_auth_user_ = user; + basic_auth_password_ = password; + return *this; + } // Synchronous (blocking) call, should be implemented for each platform // @returns true if connection was made and server returned something (200, 404, etc.). diff --git a/3party/Alohalytics/src/posix/http_client_curl.cc b/3party/Alohalytics/src/posix/http_client_curl.cc index f4b10459be..03dd9a3bdb 100644 --- a/3party/Alohalytics/src/posix/http_client_curl.cc +++ b/3party/Alohalytics/src/posix/http_client_curl.cc @@ -74,17 +74,20 @@ std::string RunCurl(const std::string& cmd) { bool HTTPClientPlatformWrapper::RunHTTPRequest() { // Last 3 chars in server's response will be http status code static constexpr size_t kCurlHttpCodeSize = 3; - std::string cmd = "curl --max-redirs 0 -s -w '%{http_code}' "; + std::string cmd = "curl --max-redirs 0 -s -w '%{http_code}' -X " + http_method_ + " "; if (!content_type_.empty()) { cmd += "-H 'Content-Type: " + content_type_ + "' "; } if (!content_encoding_.empty()) { cmd += "-H 'Content-Encoding: " + content_encoding_ + "' "; } + if (!basic_auth_user_.empty()) { + cmd += "-u '" + basic_auth_user_ + ":" + basic_auth_password_ + "' "; + } ScopedTmpFileDeleter deleter; - if (!post_body_.empty()) { -// POST body through tmp file to avoid breaking command line. + if (!body_data_.empty()) { + // POST body through tmp file to avoid breaking command line. #ifdef _MSC_VER char tmp_file[L_tmpnam]; ::tmpnam_s(tmp_file, L_tmpnam); @@ -98,14 +101,14 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { ::close(fd); #endif deleter.file = tmp_file; - if (!(std::ofstream(deleter.file) << post_body_).good()) { + if (!(std::ofstream(deleter.file) << body_data_).good()) { std::cerr << "Error: failed to write into a temporary file." << std::endl; return false; } - post_file_ = deleter.file; + body_file_ = deleter.file; } - if (!post_file_.empty()) { - cmd += "--data-binary @" + post_file_ + " "; + if (!body_file_.empty()) { + cmd += "--data-binary @" + body_file_ + " "; } cmd += url_requested_;