raw headers for http client

This commit is contained in:
Arsentiy Milchakov 2016-12-01 16:04:18 +03:00
parent 226919811b
commit 2b3eef2c5b
8 changed files with 222 additions and 191 deletions

View file

@ -19,9 +19,11 @@ jclass g_bookmarkClazz;
jclass g_myTrackerClazz;
jclass g_httpClientClazz;
jclass g_httpParamsClazz;
jclass g_httpHeaderClazz;
jclass g_platformSocketClazz;
jclass g_utilsClazz;
jclass g_bannerClazz;
jclass g_arrayListClazz;
extern "C"
{
@ -38,6 +40,7 @@ JNI_OnLoad(JavaVM * jvm, void *)
g_myTrackerClazz = jni::GetGlobalClassRef(env, "com/my/tracker/MyTracker");
g_httpClientClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/HttpClient");
g_httpParamsClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/HttpClient$Params");
g_httpHeaderClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/HttpClient$HttpHeader");
g_platformSocketClazz = jni::GetGlobalClassRef(env, "com/mapswithme/maps/location/PlatformSocket");
g_utilsClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/Utils");
g_bannerClazz = jni::GetGlobalClassRef(env, "com/mapswithme/maps/bookmarks/data/Banner");
@ -55,6 +58,7 @@ JNI_OnUnload(JavaVM *, void *)
env->DeleteGlobalRef(g_myTrackerClazz);
env->DeleteGlobalRef(g_httpClientClazz);
env->DeleteGlobalRef(g_httpParamsClazz);
env->DeleteGlobalRef(g_httpHeaderClazz);
env->DeleteGlobalRef(g_platformSocketClazz);
env->DeleteGlobalRef(g_utilsClazz);
env->DeleteGlobalRef(g_bannerClazz);

View file

@ -14,6 +14,7 @@ extern jclass g_bookmarkClazz;
extern jclass g_myTrackerClazz;
extern jclass g_httpClientClazz;
extern jclass g_httpParamsClazz;
extern jclass g_httpHeaderClazz;
extern jclass g_platformSocketClazz;
extern jclass g_utilsClazz;
extern jclass g_bannerClazz;

View file

@ -58,9 +58,10 @@ void RethrowOnJniException(ScopedEnv & env)
MYTHROW(JniException, ());
}
jfieldID GetHttpParamsFieldId(ScopedEnv & env, const char * name)
jfieldID GetHttpParamsFieldId(ScopedEnv & env, const char * name,
const char * signature = "Ljava/lang/String;")
{
return env->GetFieldID(g_httpParamsClazz, name, "Ljava/lang/String;");
return env->GetFieldID(g_httpParamsClazz, name, signature);
}
// Set string value to HttpClient.Params object, throws JniException and
@ -76,6 +77,12 @@ void SetString(ScopedEnv & env, jobject params, jfieldID const fieldId, string c
RethrowOnJniException(env);
}
void SetBoolean(ScopedEnv & env, jobject params, jfieldID const fieldId, bool const value)
{
env->SetBooleanField(params, fieldId, value);
RethrowOnJniException(env);
}
// Get string value from HttpClient.Params object, throws JniException.
void GetString(ScopedEnv & env, jobject const params, jfieldID const fieldId, string & result)
{
@ -86,6 +93,66 @@ void GetString(ScopedEnv & env, jobject const params, jfieldID const fieldId, st
result = jni::ToNativeString(env.get(), wrappedValue.get());
}
void GetInt(ScopedEnv & env, jobject const params, jfieldID const fieldId, int & result)
{
result = env->GetIntField(params, fieldId);
RethrowOnJniException(env);
}
void SetHeaders(ScopedEnv & env, jobject const params,
unordered_map<string, string> const & headers)
{
if (headers.empty())
return;
static jmethodID const headerInit = jni::GetConstructorID(
env.get(), g_httpHeaderClazz, "(Ljava/lang/String;Ljava/lang/String;)V");
static jmethodID const setHeaders = env->GetMethodID(
g_httpParamsClazz, "setHeaders", "([Lcom/mapswithme/util/HttpClient$HttpHeader;)V");
RethrowOnJniException(env);
using HeaderPair = unordered_map<string, string>::value_type;
env->CallVoidMethod(
params, setHeaders,
jni::ToJavaArray(env.get(), g_httpHeaderClazz, headers, [](JNIEnv * env,
HeaderPair const & item) {
return env->NewObject(g_httpHeaderClazz, headerInit,
jni::TScopedLocalRef(env, jni::ToJavaString(env, item.first)).get(),
jni::TScopedLocalRef(env, jni::ToJavaString(env, item.second)).get());
}));
RethrowOnJniException(env);
}
void LoadHeaders(ScopedEnv & env, jobject const params, unordered_map<string, string> & headers)
{
static jmethodID const getHeaders =
env->GetMethodID(g_httpParamsClazz, "getHeaders", "()[Ljava/lang/Object;");
static jfieldID const keyId = env->GetFieldID(g_httpHeaderClazz, "key", "Ljava/lang/String;");
static jfieldID const valueId = env->GetFieldID(g_httpHeaderClazz, "value", "Ljava/lang/String;");
jobjectArray const headersArray =
static_cast<jobjectArray>(env->CallObjectMethod(params, getHeaders));
RethrowOnJniException(env);
headers.clear();
int const length = env->GetArrayLength(headersArray);
for (size_t i = 0; i < length; ++i)
{
jobject headerEntry = env->GetObjectArrayElement(headersArray, i);
jni::ScopedLocalRef<jstring> const key(
env.get(), static_cast<jstring>(env->GetObjectField(headerEntry, keyId)));
jni::ScopedLocalRef<jstring> const value(
env.get(), static_cast<jstring>(env->GetObjectField(headerEntry, valueId)));
headers.emplace(jni::ToNativeString(env.get(), key.get()),
jni::ToNativeString(env.get(), value.get()));
}
RethrowOnJniException(env);
}
class Ids
{
public:
@ -93,15 +160,13 @@ public:
{
m_fieldIds =
{{"httpMethod", GetHttpParamsFieldId(env, "httpMethod")},
{"contentType", GetHttpParamsFieldId(env, "contentType")},
{"contentEncoding", GetHttpParamsFieldId(env, "contentEncoding")},
{"userAgent", GetHttpParamsFieldId(env, "userAgent")},
{"inputFilePath", GetHttpParamsFieldId(env, "inputFilePath")},
{"outputFilePath", GetHttpParamsFieldId(env, "outputFilePath")},
{"basicAuthUser", GetHttpParamsFieldId(env, "basicAuthUser")},
{"basicAuthPassword", GetHttpParamsFieldId(env, "basicAuthPassword")},
{"cookies", GetHttpParamsFieldId(env, "cookies")},
{"receivedUrl", GetHttpParamsFieldId(env, "receivedUrl")}};
{"receivedUrl", GetHttpParamsFieldId(env, "receivedUrl")},
{"followRedirects", GetHttpParamsFieldId(env, "followRedirects", "Z")},
{"loadHeaders", GetHttpParamsFieldId(env, "loadHeaders", "Z")},
{"httpResponseCode", GetHttpParamsFieldId(env, "httpResponseCode", "I")}};
}
jfieldID GetId(string const & fieldName) const
@ -136,7 +201,7 @@ bool HttpClient::RunHttpRequest()
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
static jmethodID const httpParamsConstructor =
env->GetMethodID(g_httpParamsClazz, "<init>", "(Ljava/lang/String;)V");
jni::GetConstructorID(env.get(), g_httpParamsClazz, "(Ljava/lang/String;)V");
jni::ScopedLocalRef<jobject> const httpParamsObject(
env.get(), env->NewObject(g_httpParamsClazz, httpParamsConstructor, jniUrl.get()));
@ -163,29 +228,19 @@ bool HttpClient::RunHttpRequest()
try
{
SetString(env, httpParamsObject.get(), ids.GetId("httpMethod"), m_httpMethod);
SetString(env, httpParamsObject.get(), ids.GetId("contentType"), m_contentType);
SetString(env, httpParamsObject.get(), ids.GetId("contentEncoding"), m_contentEncoding);
SetString(env, httpParamsObject.get(), ids.GetId("userAgent"), m_userAgent);
SetString(env, httpParamsObject.get(), ids.GetId("inputFilePath"), m_inputFile);
SetString(env, httpParamsObject.get(), ids.GetId("outputFilePath"), m_outputFile);
SetString(env, httpParamsObject.get(), ids.GetId("basicAuthUser"), m_basicAuthUser);
SetString(env, httpParamsObject.get(), ids.GetId("basicAuthPassword"), m_basicAuthPassword);
SetString(env, httpParamsObject.get(), ids.GetId("cookies"), m_cookies);
SetBoolean(env, httpParamsObject.get(), ids.GetId("followRedirects"), m_handleRedirects);
SetBoolean(env, httpParamsObject.get(), ids.GetId("loadHeaders"), m_loadHeaders);
SetHeaders(env, httpParamsObject.get(), m_headers);
}
catch (JniException const & ex)
{
return false;
}
static jfieldID const debugModeField = env->GetFieldID(g_httpParamsClazz, "debugMode", "Z");
env->SetBooleanField(httpParamsObject.get(), debugModeField, m_debugMode);
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
static jfieldID const followRedirectsField =
env->GetFieldID(g_httpParamsClazz, "followRedirects", "Z");
env->SetBooleanField(httpParamsObject.get(), followRedirectsField, m_handleRedirects);
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
static jmethodID const httpClientClassRun =
env->GetStaticMethodID(g_httpClientClazz, "run",
"(Lcom/mapswithme/util/HttpClient$Params;)Lcom/mapswithme/util/HttpClient$Params;");
@ -196,33 +251,17 @@ bool HttpClient::RunHttpRequest()
env->CallStaticObjectMethod(g_httpClientClazz, httpClientClassRun, httpParamsObject.get());
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
static jfieldID const httpResponseCodeField =
env->GetFieldID(g_httpParamsClazz, "httpResponseCode", "I");
m_errorCode = env->GetIntField(response, httpResponseCodeField);
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
try
{
GetInt(env, response, ids.GetId("httpResponseCode"), m_errorCode);
GetString(env, response, ids.GetId("receivedUrl"), m_urlReceived);
GetString(env, response, ids.GetId("contentType"), m_contentTypeReceived);
GetString(env, response, ids.GetId("contentEncoding"), m_contentEncodingReceived);
::LoadHeaders(env, httpParamsObject.get(), m_headers);
}
catch (JniException const & ex)
{
return false;
}
// Note: cookies field is used not only to send Cookie header, but also to receive back
// Server-Cookie header. CookiesField is already cached above.
jni::ScopedLocalRef<jstring> const jniServerCookies(
env.get(), static_cast<jstring>(env->GetObjectField(response, ids.GetId("cookies"))));
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
if (jniServerCookies)
{
m_serverCookies =
NormalizeServerCookies(jni::ToNativeString(env.get(), jniServerCookies.get()));
}
// dataField is already cached above.
jni::ScopedLocalRef<jbyteArray> const jniData(
env.get(), static_cast<jbyteArray>(env->GetObjectField(response, dataField)));

View file

@ -26,8 +26,9 @@ package com.mapswithme.util;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import com.mapswithme.util.log.DebugLogger;
import com.mapswithme.util.log.Logger;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@ -39,6 +40,8 @@ import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ -46,9 +49,9 @@ public final class HttpClient
{
// TODO(AlexZ): tune for larger files
private final static int STREAM_BUFFER_SIZE = 1024 * 64;
private final static String TAG = "Alohalytics-Http";
// Globally accessible for faster unit-testing
public static int TIMEOUT_IN_MILLISECONDS = 30000;
private static final Logger LOGGER = new DebugLogger(HttpClient.class.getSimpleName());
public static Params run(@NonNull final Params p) throws IOException, NullPointerException
{
@ -56,8 +59,8 @@ public final class HttpClient
throw new IllegalArgumentException("Please set valid HTTP method for request at Params.httpMethod field.");
HttpURLConnection connection = null;
if (p.debugMode)
Log.d(TAG, "Connecting to " + p.url);
LOGGER.d("Connecting to ", p.url);
try
{
connection = (HttpURLConnection) new URL(p.url).openConnection(); // NullPointerException, MalformedUrlException, IOException
@ -81,31 +84,25 @@ public final class HttpClient
connection.setReadTimeout(TIMEOUT_IN_MILLISECONDS);
connection.setUseCaches(false);
connection.setRequestMethod(p.httpMethod);
if (!TextUtils.isEmpty(p.basicAuthUser))
{
final String encoded = Base64.encodeToString((p.basicAuthUser + ":" + p.basicAuthPassword).getBytes(), Base64.NO_WRAP);
connection.setRequestProperty("Authorization", "Basic " + encoded);
}
if (!TextUtils.isEmpty(p.userAgent))
connection.setRequestProperty("User-Agent", p.userAgent);
if (!TextUtils.isEmpty(p.cookies))
connection.setRequestProperty("Cookie", p.cookies);
for (HttpHeader header : p.headers)
{
connection.setRequestProperty(header.key, header.value);
}
if (!TextUtils.isEmpty(p.inputFilePath) || p.data != null)
{
// Send (POST, PUT...) data to the server.
if (TextUtils.isEmpty(p.contentType))
if (TextUtils.isEmpty(connection.getRequestProperty("Content-Type")))
throw new NullPointerException("Please set Content-Type for request.");
// Work-around for situation when more than one consequent POST requests can lead to stable
// "java.net.ProtocolException: Unexpected status line:" on a client and Nginx HTTP 499 errors.
// The only found reference to this bug is http://stackoverflow.com/a/24303115/1209392
connection.setRequestProperty("Connection", "close");
connection.setRequestProperty("Content-Type", p.contentType);
if (!TextUtils.isEmpty(p.contentEncoding))
connection.setRequestProperty("Content-Encoding", p.contentEncoding);
connection.setDoOutput(true);
if (p.data != null)
{
@ -119,8 +116,7 @@ public final class HttpClient
{
os.close();
}
if (p.debugMode)
Log.d(TAG, "Sent " + p.httpMethod + " with content of size " + p.data.length);
LOGGER.d("Sent ", p.httpMethod, " with content of size ", p.data.length);
}
else
{
@ -136,27 +132,35 @@ public final class HttpClient
}
istream.close(); // IOException
ostream.close(); // IOException
if (p.debugMode)
Log.d(TAG, "Sent " + p.httpMethod + " with file of size " + file.length());
LOGGER.d("Sent ", p.httpMethod, " with file of size ", file.length());
}
}
// 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.");
LOGGER.d("Received HTTP ", p.httpResponseCode, " from server.");
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();
final Map<String, List<String>> headers = connection.getHeaderFields();
if (headers != null && headers.containsKey("Set-Cookie"))
p.headers.clear();
if (p.loadHeaders)
{
// Multiple Set-Cookie headers are normalized in C++ code.
p.cookies = android.text.TextUtils.join(", ", headers.get("Set-Cookie"));
for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet())
{
// Some implementations include a mapping for the null key.
if (header.getKey() == null || header.getValue() == null)
continue;
p.headers.add(new HttpHeader(header.getKey(), TextUtils.join(", ", header.getValue())));
}
}
else
{
List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
if (cookies != null)
p.headers.add(new HttpHeader("Set-Cookie", TextUtils.join(", ", cookies)));
}
// This implementation receives any data only if we have HTTP::OK (200).
if (p.httpResponseCode == HttpURLConnection.HTTP_OK)
@ -191,31 +195,48 @@ public final class HttpClient
return p;
}
public static class Params
private static class HttpHeader
{
public HttpHeader(@NonNull String key, @NonNull String value)
{
this.key = key;
this.value = value;
}
public String key;
public String value;
}
private static class Params
{
public void setHeaders(@NonNull HttpHeader[] array)
{
headers = new ArrayList<>(Arrays.asList(array));
}
public Object[] getHeaders()
{
return headers.toArray();
}
public String url;
// Can be different from url in case of redirects.
public String receivedUrl;
public String httpMethod;
// Should be specified for any request whose method allows non-empty body.
// On return, contains received Content-Type or null.
public String contentType;
// Can be specified for any request whose method allows non-empty body.
// On return, contains received Content-Encoding or null.
public String contentEncoding;
public byte[] data;
// Send from input file if specified instead of data.
public String inputFilePath;
// Received data is stored here if not null or in data otherwise.
public String outputFilePath;
// Optionally client can override default HTTP User-Agent.
public String userAgent;
public String basicAuthUser;
public String basicAuthPassword;
public String cookies;
public ArrayList<HttpHeader> headers = new ArrayList<>();
public int httpResponseCode = -1;
public boolean debugMode = false;
public boolean followRedirects = true;
public boolean loadHeaders;
// Simple GET request constructor.
public Params(String url)
@ -223,19 +244,5 @@ public final class HttpClient
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;
}
}
}

View file

@ -1,17 +1,13 @@
#include "platform/http_client.hpp"
#include "coding/base64.hpp"
#include "base/string_utils.hpp"
#include "std/sstream.hpp"
namespace platform
{
HttpClient & HttpClient::SetDebugMode(bool debug_mode)
{
m_debugMode = debug_mode;
return *this;
}
HttpClient & HttpClient::SetUrlRequested(string const & url)
{
m_urlRequested = url;
@ -30,9 +26,9 @@ HttpClient & HttpClient::SetBodyFile(string const & body_file, string const & co
{
m_inputFile = body_file;
m_bodyData.clear();
m_contentType = content_type;
m_headers.emplace("Content-Type", content_type);
m_httpMethod = http_method;
m_contentEncoding = content_encoding;
m_headers.emplace("Content-Encoding", content_encoding);
return *this;
}
@ -42,16 +38,9 @@ HttpClient & HttpClient::SetReceivedFile(string const & received_file)
return *this;
}
HttpClient & HttpClient::SetUserAgent(string const & user_agent)
{
m_userAgent = user_agent;
return *this;
}
HttpClient & HttpClient::SetUserAndPassword(string const & user, string const & password)
{
m_basicAuthUser = user;
m_basicAuthPassword = password;
m_headers.emplace("Authorization", "Basic" + base64::Encode(user + ":" + password));
return *this;
}
@ -67,6 +56,12 @@ HttpClient & HttpClient::SetHandleRedirects(bool handle_redirects)
return *this;
}
HttpClient & HttpClient::SetRawHeader(string const & key, string const & value)
{
m_headers.emplace(key, value);
return *this;
}
string const & HttpClient::UrlRequested() const
{
return m_urlRequested;
@ -99,13 +94,18 @@ string const & HttpClient::HttpMethod() const
string HttpClient::CombinedCookies() const
{
if (m_serverCookies.empty())
string serverCookies;
auto const it = m_headers.find("Set-Cookie");
if (it != m_headers.end())
serverCookies = it->second;
if (serverCookies.empty())
return m_cookies;
if (m_cookies.empty())
return m_serverCookies;
return serverCookies;
return m_serverCookies + "; " + m_cookies;
return serverCookies + "; " + m_cookies;
}
string HttpClient::CookieByName(string name) const
@ -120,6 +120,16 @@ string HttpClient::CookieByName(string name) const
return {};
}
void HttpClient::LoadHeaders(bool loadHeaders)
{
m_loadHeaders = loadHeaders;
}
unordered_map<string, string> const & HttpClient::GetHeaders() const
{
return m_headers;
}
// static
string HttpClient::NormalizeServerCookies(string && cookies)
{

View file

@ -26,6 +26,7 @@ SOFTWARE.
#include "base/macros.hpp"
#include "std/string.hpp"
#include "std/unordered_map.hpp"
namespace platform
{
@ -53,7 +54,6 @@ public:
string const & content_encoding = "");
// If set, stores server reply in file specified.
HttpClient & SetReceivedFile(string const & received_file);
HttpClient & SetUserAgent(string const & user_agent);
// This method is mutually exclusive with set_body_file().
template <typename StringT>
HttpClient & SetBodyData(StringT && body_data, string const & content_type,
@ -62,9 +62,9 @@ public:
{
m_bodyData = forward<StringT>(body_data);
m_inputFile.clear();
m_contentType = content_type;
m_headers.emplace("Content-Type", content_type);
m_httpMethod = http_method;
m_contentEncoding = content_encoding;
m_headers.emplace("Content-Encoding", content_encoding);
return *this;
}
// HTTP Basic Auth.
@ -74,6 +74,7 @@ public:
// When set to true (default), clients never get 3XX codes from servers, redirects are handled automatically.
// TODO: "false" is now supported on Android only.
HttpClient & SetHandleRedirects(bool handle_redirects);
HttpClient & SetRawHeader(string const & key, string const & value);
string const & UrlRequested() const;
// @returns empty string in the case of error
@ -88,6 +89,8 @@ public:
string CombinedCookies() const;
// Returns cookie value or empty string if it's not present.
string CookieByName(string name) const;
void LoadHeaders(bool loadHeaders);
unordered_map<string, string> const & GetHeaders() const;
private:
// Internal helper to convert cookies like this:
@ -105,23 +108,13 @@ private:
string m_outputFile;
// Data we received from the server if output_file_ wasn't initialized.
string m_serverResponse;
string m_contentType;
string m_contentTypeReceived;
string m_contentEncoding;
string m_contentEncodingReceived;
string m_userAgent;
string m_bodyData;
string m_httpMethod = "GET";
string m_basicAuthUser;
string m_basicAuthPassword;
// All Set-Cookie values from server response combined in a Cookie format:
// cookie1=value1; cookie2=value2
// TODO(AlexZ): Support encoding and expiration/path/domains etc.
string m_serverCookies;
// Cookies set by the client before request is run.
string m_cookies;
bool m_debugMode = false;
unordered_map<string, string> m_headers;
bool m_handleRedirects = true;
bool m_loadHeaders = false;
DISALLOW_COPY_AND_MOVE(HttpClient);
};

View file

@ -61,14 +61,10 @@ bool HttpClient::RunHttpRequest()
request.HTTPShouldHandleCookies = NO;
request.HTTPMethod = [NSString stringWithUTF8String:m_httpMethod.c_str()];
if (!m_contentType.empty())
[request setValue:[NSString stringWithUTF8String:m_contentType.c_str()] forHTTPHeaderField:@"Content-Type"];
if (!m_contentEncoding.empty())
[request setValue:[NSString stringWithUTF8String:m_contentEncoding.c_str()] forHTTPHeaderField:@"Content-Encoding"];
if (!m_userAgent.empty())
[request setValue:[NSString stringWithUTF8String:m_userAgent.c_str()] forHTTPHeaderField:@"User-Agent"];
for (auto const & header : m_headers)
{
[request setValue:@(header.second.c_str()) forHTTPHeaderField:@(header.first.c_str())];
}
if (!m_cookies.empty())
[request setValue:[NSString stringWithUTF8String:m_cookies.c_str()] forHTTPHeaderField:@"Cookie"];
@ -77,17 +73,10 @@ bool HttpClient::RunHttpRequest()
[request setValue:gBrowserUserAgent forHTTPHeaderField:@"User-Agent"];
#endif // TARGET_OS_IPHONE
if (!m_basicAuthUser.empty())
{
NSData * loginAndPassword = [[NSString stringWithUTF8String:(m_basicAuthUser + ":" + m_basicAuthPassword).c_str()] dataUsingEncoding:NSUTF8StringEncoding];
[request setValue:[NSString stringWithFormat:@"Basic %@", [loginAndPassword base64EncodedStringWithOptions:0]] forHTTPHeaderField:@"Authorization"];
}
if (!m_bodyData.empty())
{
request.HTTPBody = [NSData dataWithBytes:m_bodyData.data() length:m_bodyData.size()];
if (m_debugMode)
LOG(LINFO, ("Uploading buffer of size", m_bodyData.size(), "bytes"));
LOG(LDEBUG, ("Uploading buffer of size", m_bodyData.size(), "bytes"));
}
else if (!m_inputFile.empty())
{
@ -97,38 +86,39 @@ bool HttpClient::RunHttpRequest()
if (err)
{
m_errorCode = static_cast<int>(err.code);
if (m_debugMode)
LOG(LERROR, ("Error: ", m_errorCode, [err.localizedDescription UTF8String]));
LOG(LDEBUG, ("Error: ", m_errorCode, [err.localizedDescription UTF8String]));
return false;
}
request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:path];
[request setValue:[NSString stringWithFormat:@"%llu", file_size] forHTTPHeaderField:@"Content-Length"];
if (m_debugMode)
LOG(LINFO, ("Uploading file", m_inputFile, file_size, "bytes"));
LOG(LDEBUG, ("Uploading file", m_inputFile, file_size, "bytes"));
}
NSHTTPURLResponse * response = nil;
NSError * err = nil;
NSData * url_data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
m_headers.clear();
if (response)
{
m_errorCode = static_cast<int>(response.statusCode);
m_urlReceived = [response.URL.absoluteString UTF8String];
NSString * content = [response.allHeaderFields objectForKey:@"Content-Type"];
if (content)
m_contentTypeReceived = std::move([content UTF8String]);
NSString * encoding = [response.allHeaderFields objectForKey:@"Content-Encoding"];
if (encoding)
m_contentEncodingReceived = std::move([encoding UTF8String]);
// Apple merges all Set-Cookie fields into one NSDictionary key delimited by commas.
NSString * cookies = [response.allHeaderFields objectForKey:@"Set-Cookie"];
if (cookies)
m_serverCookies = NormalizeServerCookies(std::move([cookies UTF8String]));
if (m_loadHeaders)
{
[response.allHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSString * obj, BOOL * stop)
{
m_headers.emplace(key.UTF8String, obj.UTF8String);
}];
}
else
{
NSString * cookies = [response.allHeaderFields objectForKey:@"Set-Cookie"];
if (cookies)
m_headers.emplace("Set-Cookie", NormalizeServerCookies(std::move([cookies UTF8String])));
}
if (url_data)
{
@ -138,6 +128,7 @@ bool HttpClient::RunHttpRequest()
[url_data writeToFile:[NSString stringWithUTF8String:m_outputFile.c_str()] atomically:YES];
}
return true;
}
// Request has failed if we are here.
@ -150,8 +141,7 @@ bool HttpClient::RunHttpRequest()
}
m_errorCode = static_cast<int>(err.code);
if (m_debugMode)
LOG(LERROR, ("Error: ", m_errorCode, ':', [err.localizedDescription UTF8String], "while connecting to", m_urlRequested));
LOG(LDEBUG, ("Error: ", m_errorCode, ':', [err.localizedDescription UTF8String], "while connecting to", m_urlRequested));
return false;
}

View file

@ -126,7 +126,7 @@ Headers ParseHeaders(string const & raw)
auto const delims = line.find(": ");
if (delims != string::npos)
headers.push_back(make_pair(line.substr(0, delims), line.substr(delims + 2)));
headers.emplace_back(line.substr(0, delims), line.substr(delims + 2));
}
return headers;
}
@ -160,14 +160,10 @@ bool HttpClient::RunHttpRequest()
string cmd = "curl -s -w '%{http_code}' -X " + m_httpMethod + " -D '" + headers_deleter.m_fileName + "' ";
if (!m_contentType.empty())
cmd += "-H 'Content-Type: " + m_contentType + "' ";
if (!m_contentEncoding.empty())
cmd += "-H 'Content-Encoding: " + m_contentEncoding + "' ";
if (!m_basicAuthUser.empty())
cmd += "-u '" + m_basicAuthUser + ":" + m_basicAuthPassword + "' ";
for (auto const & header : m_headers)
{
cmd += "-H '" + header.first + ": " + header.second + "' ";
}
if (!m_cookies.empty())
cmd += "-b '" + m_cookies + "' ";
@ -197,11 +193,7 @@ bool HttpClient::RunHttpRequest()
cmd += "-o " + rfile + strings::to_string(" ") + "'" + m_urlRequested + "'";
if (m_debugMode)
{
LOG(LINFO, ("Executing", cmd));
}
LOG(LDEBUG, ("Executing", cmd));
try
{
@ -213,27 +205,25 @@ bool HttpClient::RunHttpRequest()
return false;
}
m_headers.clear();
Headers const headers = ParseHeaders(ReadFileAsString(headers_deleter.m_fileName));
string serverCookies;
for (auto const & header : headers)
{
if (header.first == "Set-Cookie")
{
m_serverCookies += header.second + ", ";
serverCookies += header.second + ", ";
}
else if (header.first == "Content-Type")
else
{
m_contentTypeReceived = header.second;
}
else if (header.first == "Content-Encoding")
{
m_contentEncodingReceived = header.second;
}
else if (header.first == "Location")
{
m_urlReceived = header.second;
if (header.first == "Location")
m_urlReceived = header.second;
if (m_loadHeaders)
m_headers.emplace(header.first, header.second);
}
}
m_serverCookies = NormalizeServerCookies(move(m_serverCookies));
m_headers.emplace("Set-Cookie", NormalizeServerCookies(move(serverCookies)));
if (m_urlReceived.empty())
{
@ -247,8 +237,7 @@ bool HttpClient::RunHttpRequest()
{
// Handle HTTP redirect.
// TODO(AlexZ): Should we check HTTP redirect code here?
if (m_debugMode)
LOG(LINFO, ("HTTP redirect", m_errorCode, "to", m_urlReceived));
LOG(LDEBUG, ("HTTP redirect", m_errorCode, "to", m_urlReceived));
HttpClient redirect(m_urlReceived);
redirect.SetCookies(CombinedCookies());
@ -261,10 +250,8 @@ bool HttpClient::RunHttpRequest()
m_errorCode = redirect.ErrorCode();
m_urlReceived = redirect.UrlReceived();
m_serverCookies = move(redirect.m_serverCookies);
m_headers = move(redirect.m_headers);
m_serverResponse = move(redirect.m_serverResponse);
m_contentTypeReceived = move(redirect.m_contentTypeReceived);
m_contentEncodingReceived = move(redirect.m_contentEncodingReceived);
}
return true;