3#include "absl/status/status.h"
4#include "absl/strings/str_cat.h"
7#include <TargetConditionals.h>
10#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
12#import <Foundation/Foundation.h>
17NSString* ToNSString(
const std::string& value) {
21 return [NSString stringWithUTF8String:value.c_str()];
24std::string ToStdString(NSString* value) {
28 const char* cstr = [value UTF8String];
29 return cstr ? std::string(cstr) : std::string();
35 const std::string& method,
const std::string& url,
36 const std::map<std::string, std::string>& headers,
37 const std::string& body,
int timeout_ms) {
39 return absl::InvalidArgumentError(
"UrlSessionHttpRequest: empty method");
42 return absl::InvalidArgumentError(
"UrlSessionHttpRequest: empty url");
46 NSString* url_string = ToNSString(url);
47 NSURL* ns_url = [NSURL URLWithString:url_string];
49 return absl::InvalidArgumentError(
50 absl::StrCat(
"UrlSessionHttpRequest: invalid url: ", url));
53 NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:ns_url];
54 request.HTTPMethod = ToNSString(method);
56 request.timeoutInterval =
static_cast<NSTimeInterval
>(timeout_ms) / 1000.0;
59 for (
const auto& [key, value] : headers) {
60 NSString* header_key = ToNSString(key);
61 NSString* header_value = ToNSString(value);
62 if (header_key.length == 0) {
65 [request setValue:header_value forHTTPHeaderField:header_key];
69 NSData* data = [NSData dataWithBytes:body.data() length:body.size()];
70 request.HTTPBody = data;
73 __block NSData* response_data = nil;
74 __block NSURLResponse* response = nil;
75 __block NSError* error = nil;
77 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
78 NSURLSessionDataTask* task =
79 [[NSURLSession sharedSession] dataTaskWithRequest:request
80 completionHandler:^(NSData* data,
86 dispatch_semaphore_signal(sem);
90 if (timeout_ms <= 0) {
91 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
93 dispatch_time_t deadline =
94 dispatch_time(DISPATCH_TIME_NOW, (int64_t)timeout_ms * NSEC_PER_MSEC);
95 if (dispatch_semaphore_wait(sem, deadline) != 0) {
97 return absl::DeadlineExceededError(
"Request timed out");
102 std::string message = ToStdString(error.localizedDescription);
103 if (message.empty()) {
104 message =
"URLSession request failed";
106 return absl::UnavailableError(message);
109 UrlSessionHttpResponse out;
110 if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
111 NSHTTPURLResponse* http =
static_cast<NSHTTPURLResponse*
>(response);
112 out.status_code =
static_cast<int>(http.statusCode);
113 NSDictionary<NSString*, id>* all_headers = http.allHeaderFields;
114 for (
id key in all_headers) {
115 NSString* key_str = [
key isKindOfClass:[NSString
class]]
116 ?
static_cast<NSString*
>(
key)
118 NSString* value_str =
119 [[all_headers objectForKey:
key] isKindOfClass:[NSString
class]]
120 ?
static_cast<NSString*
>([all_headers objectForKey:
key])
122 out.headers[ToStdString(key_str)] = ToStdString(value_str);
127 out.body.assign(
reinterpret_cast<const char*
>(response_data.bytes),
128 static_cast<size_t>(response_data.length));
142 const std::string&,
const std::string&,
143 const std::map<std::string, std::string>&,
const std::string&,
int) {
144 return absl::UnimplementedError(
145 "UrlSessionHttpRequest is only available on iOS targets");
absl::StatusOr< UrlSessionHttpResponse > UrlSessionHttpRequest(const std::string &method, const std::string &url, const std::map< std::string, std::string > &headers, const std::string &body, int timeout_ms)