Казалось бы, тривиальнейшая задача – определить настройки прокси сервиса, заданные при помощи .pac файла либо полученные в результате работы автоконфигурации. А в действительности оказывается что информации в интернете кот наплакал, а та что есть посвящена венде (что еще печальнее). Чтобы исправить эту жутко не справедливую ситуацию, я опишу как можно определить настройки прокси в Mac OS X приложении.
Итак, необходимый API появился в Mac OS X начиная с версии 10.5, так же API присутствует в iOS, но деталей по версиям я уже сказать не могу.
Необходимая информация запрашивается из двух источников – системных настроек и сетевой подсистемы. Системные настройки позволяют понять, используется ли прокси вообще и не задан ли дрес пользователем “в ручную”. В коде это будет выглядеть примерно так:
if( !array )
return;
if( CFDictionaryContainsKey( array, kSCPropNetProxiesProxyAutoConfigEnable ) )
detect_auto_proxy( array, site, pinfo );
else
detect_user_proxy( array, pinfo );
Для того чтоб было понятнее, о чем ниже идет речь, вот определение пары структур, используемых в примере:
{
CFStringRef addr_;
CFNumberRef port_;
};
struct proxy_callback
{
proxy_info pinfo;
bool finished_;
};
Функция SCDynamicStoreCopyProxies экспортируется из фрэймворка SystemConfiguration и служит для получения словаря с системными настройками. В случае если включена автоконфигурация, то по ключу kSCPropNetProxiesProxyAutoConfigEnable будет находиться соответствующее значение, если автоконфигурация выключена то значения соответствующее ключу не будет существовать.
В том случае, если адрес прокси сервера был задан пользователем “в ручную”, то количество дополнительных действий минимально, достаточно считать значения соответствующие ключам HTTPProxy и HTTPPort (HTTPSProxy и HTTPSPort соответственно, для HTTPS прокси).
{
pinfo.addr_ = (CFStringRef)CFDictionaryGetValue( array, CFSTR("HTTPProxy") );
pinfo.port_ = (CFNumberRef)CFDictionaryGetValue( array, CFSTR("HTTPPort") );
}
А вот в случае с автоконфигурированием или .pac файлом все несколько сложнее. Первым делом необходимо получить путь к файлу автоконфигурации, который будет существовать как в случае если он был задан явно (путь к .pac файлу), так и в случае с автоконфигурацией прокси.
После этого, необходимо вызвать функцию CFNetworkExecuteProxyAutoConfigurationURL, которая принимает на вход путь к файлу конфигурации в виде CURLRef, url к которому мы хотим обратиться, callback функцию и указатель на структуру CFStreamClientContext. В результате вызова создается объект типа CFRunLoopSourceRef, который и необходимо выполнить для получения искомых настроек. Да, относительно указателя на структуру CFStreamClientContext в функции CFNetworkExecuteProxyAutoConfigurationURL; этот параметр не должен быть равен NULL, даже если он не используется в дальнейшем, т.к. нулевой значение приводит к ошибке доступа.
О том как использовать RunLoop можно написать отдельный большой пост, поэтому я просто покажу как выполнить полученный CFRunLoopSourceRef объект наиболее простым и примитивным способом:
CFRunLoopAddSource(CFRunLoopGetCurrent(), loopSource, mode);
if (loopSource) CFRelease(loopSource);
do {
CFRunLoopRunInMode(mode, 0.1, true);
} while (false == callback_info.finished_);
После того как настройки прокси-сервера будут определены, вызывается callback функция, переданная в CFNetworkExecuteProxyAutoConfigurationURL. И уже в этой функции необходимо получить соответствующую информацию:
{
proxy_callback *info = (proxy_callback*)client;
CFDictionaryRef firstProxy = CFArrayGetCount(proxyList) ?
(CFDictionaryRef)CFArrayGetValueAtIndex(proxyList, 0) :
(CFDictionaryRef)0;
info->pinfo.addr_ = (CFStringRef)CFDictionaryGetValue( firstProxy, CFSTR("kCFProxyHostNameKey") );
info->pinfo.port_ = (CFNumberRef)CFDictionaryGetValue( firstProxy, CFSTR("kCFProxyPortNumberKey") );
info->finished_ = true;
}
Код иллюстрирующий этот подход можно скачать тут.