Вступление
Всякому известно, что Win32 API не отличается чрезмерным удобством. Например, для вызова функции частенько приходится заполнять большую структуру с полями вроде
cbSize
, dwFlags
и т. д. Или для получения строки сначала узнавать размер ее, готовить буфер и затем лишь получать саму строку. Далее пойдет речь про функцию ::HttpQueryInfo()
и применение идиомы «traits» для упрощения работы с ней.Прямолинейный способ
Код взят из MSDN:
// Retrieving Headers Using a Constant
BOOL SampleCodeOne(HINTERNET hHttp)
{
LPVOID lpOutBuffer=NULL;
DWORD dwSize = 0;
retry:
// This call will fail on the first pass, because
// no buffer is allocated.
if(!HttpQueryInfo(hHttp,HTTP_QUERY_RAW_HEADERS_CRLF,
(LPVOID)lpOutBuffer,&dwSize,NULL))
{
if (GetLastError()==ERROR_HTTP_HEADER_NOT_FOUND)
{
// Code to handle the case where the header isn't available.
return TRUE;
}
else
{
// Check for an insufficient buffer.
if (GetLastError()==ERROR_INSUFFICIENT_BUFFER)
{
// Allocate the necessary buffer.
lpOutBuffer = new char[dwSize];
// Retry the call.
goto retry;
}
else
{
// Error handling code.
delete [] lpOutBuffer;
return FALSE;
}
}
}
delete [] lpOutBuffer;
return TRUE;
}
На голове начинают шевелиться волосы. (Кстати,
delete []
можно вызывать и для нулевого указателя).Хитрый способ
Различных заголовков у запроса довольно много, но почти все они представляют из себя либо строку, либо число. В идеале хочется получать значение заголовка в одной строке кода:
const int code = get_header<HTTP_QUERY_STATUS_CODE>(hRequest) ;
const int responseBodySize = get_header<HTTP_QUERY_CONTENT_LENGTH>(hRequest) ;
const CString allHeaders = get_header<HTTP_QUERY_RAW_HEADERS_CRLF>(hRequest) ;
Для этого тип возвращаемого значения у
get_header
должен зависеть от указанного заголовка.
template <DWORD I>
TypeDependentOfI get_header(HINTERNET aRequest)
{
return Magic(aRequest, I) ;
}
И тут на помощь приходит идиома «traits». Обычно она реализуется в виде шаблона со специализациями, в котором перечислены typedef'ы и константы, зависящие от параметров шаблона.
using ATL::CString ;
template <DWORD I>
struct header_traits ;
template <>
struct header_traits<HTTP_QUERY_STATUS_CODE>
{
typedef DWORD result_type ;
} ;
template <>
struct header_traits<HTTP_QUERY_CONTENT_LENGTH>
{
typedef DWORD result_type ;
} ;
template <>
struct header_traits<HTTP_QUERY_RAW_HEADERS_CRLF>
{
typedef CString result_type ;
} ;
Конечно, писать «в лоб» N специализаций для всех заголовков не хочется. Кроме того, отличаться будет не только тип возвращаемого значения, но и сам метод добычи этого значения. Приходится делать хитрее. Сначала выносим методы добычи в отдельные структуры (чтобы можно было на эти методы ссылаться из traits).
struct string_getter
{
typedef CString result_type ;
static type get(HINTERNET aRequest, DWORD aValueCode)
{
DWORD len = 0 ;
const BOOL r1 = ::HttpQueryInfo(aRequest, aValueCode, 0, IN OUT &len, 0) ;
if (len > 0)
{
CString res ;
const DWORD r2 = ::HttpQueryInfo(aRequest, aValueCode, OUT res.GetBufferSetLength(len + 1), IN OUT &len, 0) ;
if (r2 != 0)
return res ;
}
return CString() ;
}
} ;
struct num_getter
{
typedef DWORD result_type ;
static type get(HINTERNET aRequest, DWORD aValueCode)
{
static const int KMaxLen = 16 ;
TCHAR buf[KMaxLen + 1] ;
ZeroSizeOf(buf) ;
DWORD len = KMaxLen ;
if (::HttpQueryInfo(aRequest, aValueCode, OUT buf, IN OUT &len, 0))
{
return (DWORD)_tcstoul(buf) ;
}
return 0 ;
}
} ;
А в самих traits уже упоминаем эти структуры, в которых есть информация и о типе возвращаемого значения, и метод для добычи его.
template <>
struct header_traits<HTTP_QUERY_STATUS_CODE>
{
typedef num_getter getter ;
} ;
template <>
struct header_traits<HTTP_QUERY_CONTENT_LENGTH>
{
typedef num_getter getter ;
} ;
template <>
struct header_traits<HTTP_QUERY_RAW_HEADERS_CRLF>
{
typedef string_getter getter ;
} ;
Затем пишем удобные макросы для перечисления объявления заголовков:
#define DECLARE_STRING_HEADER_GETTER(code) \
template <> struct header_traits<code> \
{ typedef string_getter getter ; } ;
#define DECLARE_NUM_HEADER_GETTER(code) \
template <> struct header_traits<code> \
{ typedef num_getter getter ; } ;
DECLARE_NUM_HEADER_GETTER(HTTP_QUERY_STATUS_CODE)
DECLARE_NUM_HEADER_GETTER(HTTP_QUERY_CONTENT_LENGTH)
DECLARE_STRING_HEADER_GETTER(HTTP_QUERY_RAW_HEADERS_CRLF)
DECLARE_STRING_HEADER_GETTER(HTTP_QUERY_CONTENT_DISPOSITION)
// ...
#undef DECLARE_NUM_HEADER_GETTER
#undef DECLARE_STRING_HEADER_GETTER
В таком виде гораздо проще и нагляднее перечислять специализации для нужных заголовков, особенно если их не 3-4, а несколько десятков.
В итоге «волшебный» метод
get_header()
приобретает вид:
template <DWORD I>
typename header_traits<I>::getter::type get_header(HINTERNET aRequest)
{
return typename header_traits<I>::getter::get(aRequest, I) ;
}
Заключение
Вышеописанное решение претендует на звание велосипеда, но до сих пор ничего подобного мне в интернетах не попадалось. Вообще Win32 API — очень благодатная почва для таких небольших усовершенствований.
Лично меня до глубины души радует написание таких мелочей, «синтаксического сахара», которые облегчают жизнь и повышают читаемость кода.