Share via


span.sup { vertical-align:text-top; }

Windows With C++

Asynchronous WinHTTP.

Kenny Kerr

Contents

WinHTTP Overview
Session Objects
Connection Objects
Request Objects
Request Notifications
Request Cancellation
Sending Request Data
What's Next

As a result of advances in distributed programming, most modern Windows®-based applications must be able to make HTTP requests. Although HTTP is relatively simple, modern HTTP handling is not at all trivial. Asynchronous processing requires buffering potentially large requests and responses, authentication, automatic proxy server detection, persistent connections, and much more. Sure, you can ignore many of these issues but the quality of your application will suffer. And HTTP is not simple enough to imitate with TCP sockets. So what is a C++ developer to do?

A common misconception is that you need to use the Microsoft® .NET Framework if you want your application to access the Web. The truth is that developers using managed code still must deal with many of the same issues I just mentioned, and many of new styles of Web services, such as Representational State Transfer (REST), are just as easy to program using native code.

In this month's column, I am going to show you how to use the Windows HTTP Services, or WinHTTP, API for implementing HTTP clients. On Windows Vista® and Windows Server® 2008, WinHTTP additionally include support for file uploads larger than 4GB, improvements to certificate-based authentication, the ability to retrieve source and destination IP addresses, and more.

WinHTTP provides both a C API as well as a COM API. The C API is naturally harder to use at first, but with a little help from C++ it provides a very powerful and flexible API for building HTTP clients. It also provides both synchronous and asynchronous programming models. I'm going to focus on the asynchronous model for a few reasons. First, parallel programming is critical in today's concurrency-aware world. Second, too much emphasis in documentation and training in general is placed on single-threaded programming to the point where little room is left to discuss parallel programming. What little prescriptive guidance is provided tends to be unrealistic in its simplicity. Many developers shy away from parallel programming, but you'll soon discover that it can be quite natural and even fun! (OK, so your definition of fun might differ from mine, but don't be discouraged.)

WinHTTP Overview

Although the C API doesn't make it obvious, the WinHTTP API is logically modeled as three separate objects: session, connection, and request. A session object is required to initialize WinHTTP. Only one session is required per application. The session helps to create connection objects. One connection object is required for each HTTP server with which you wish to communicate. The connection object, in turn, helps to create individual request objects. An actual network connection is established the first time a request is sent. The relationship between these objects is illustrated in Figure 1. A session may have multiple active connections and a connection may have multiple concurrent requests.

fig01.gif

Figure 1 Sessions, Connections, and Requests

Session, connection, and request objects are represented by HINTERNET handles. Although different functions are used to create these objects, they are all destroyed by passing their respective handles to the WinHttpCloseHandle function. Additionally, functions such as WinHttpQueryOption and WinHttpSetOption use the handles to query and set a variety of different options supported by the WinHTTP objects.

Figure 2 lists the WinHttpHandle class that you can use to manage the lifetime of all three types of WinHTTP handles as well as query and set options on them. So, given a request object, you can retrieve its full URL with the WINHTTP_OPTION_URL option:

DWORD length = 0;

request.QueryOption(WINHTTP_OPTION_URL, 0, length);

ASSERT(ERROR_INSUFFICIENT_BUFFER == ::GetLastError());
CString url;

COM_VERIFY(request.QueryOption(WINHTTP_OPTION_URL,
   url.GetBufferSetLength(length / sizeof(WCHAR)), length));
url.ReleaseBuffer();

Figure 2 WinHttpHandle Class

class WinHttpHandle
{
public:
    WinHttpHandle() :
        m_handle(0)
    {}

    ~WinHttpHandle()
    {
        Close();
    }

    bool Attach(HINTERNET handle)
    {
        ASSERT(0 == m_h);
        m_handle = handle;
        return 0 != m_handle;
    }

    HINTERNET Detach()
    {
        HANDLE handle = m_handle;
        m_handle = 0;
        return handle;
    }

    void Close()
    {
        if (0 != m_handle)
        {   
            VERIFY(::WinHttpCloseHandle(m_handle));
            m_handle = 0;
        }
    }

    HRESULT SetOption(DWORD option,
                      const void* value,
                      DWORD length)
    {
        if (!::WinHttpSetOption(m_handle,
                                option,
                                const_cast<void*>(value),
                                length))
        {
            return HRESULT_FROM_WIN32(::GetLastError());
        }

        return S_OK;
    }

    HRESULT QueryOption(DWORD option,
                        void* value,
                        DWORD& length) const
    {
        if (!::WinHttpQueryOption(m_handle,
                                  option,
                                  value,
                                  &length))
        {
            return HRESULT_FROM_WIN32(::GetLastError());
        }

        return S_OK;
    }

    HINTERNET m_handle;
};

I use the COM_VERIFY macro in my code snippets to clearly identify where functions return an HRESULT that must be checked. You can replace this with appropriate error handling, whether that is throwing an exception or returning the HRESULT yourself.

Session Objects

A session object is created using the WinHttpOpen function. The first parameter specifies an optional agent string for your application. The next three parameters specify how WinHTTP will resolve server names. This is useful for controlling whether servers are accessed directly or through a proxy server (read more about handling proxies in the sidebar entitled "Determining Proxy Settings" below). The last parameter contains flags of which there is currently only one defined. WINHTTP_FLAG_ASYNC indicates that WinHTTP functions should operate asynchronously:

HINTERNET session = ::WinHttpOpen(0, // no agent string
                                  WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                                  WINHTTP_NO_PROXY_NAME,
                                  WINHTTP_NO_PROXY_BYPASS,
                                  WINHTTP_FLAG_ASYNC);

Figure 3 lists the WinHttpSession class that derives from the WinHttpHandle in Figure 2 and simplifies the creation of session objects. WinHttpSession creates the session with certain default values that should suffice in most cases.

Figure 3 WinHttpSession Class

class WinHttpSession : public WinHttpHandle
{
public:
    HRESULT Initialize()
    {
        if (!Attach(::WinHttpOpen(0, // no agent string
                                  WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                                  WINHTTP_NO_PROXY_NAME,
                                  WINHTTP_NO_PROXY_BYPASS,
                                  WINHTTP_FLAG_ASYNC)))
        {
            return HRESULT_FROM_WIN32(::GetLastError());
        }

        return S_OK;
    }
};

Connection Objects

A connection object is created using the WinHttpConnect function. The first parameter specifies the session to which the new connection will belong. The next one lists the name or IP address of the server. The third parameter specifies the server's port number. For this parameter, you can alternatively use INTERNET_DEFAULT_PORT, and WinHTTP will use port 80 for regular HTTP requests and port 443 for secure HTTP requests (HTTPS):

HINTERNET connection = ::WinHttpConnect(session,
                                        L"example.com",
                                        INTERNET_DEFAULT_PORT,
                                        0); // reserved

if (0 == connection)
{
    // Call GetLastError for error information
}

The code in Figure 4 shows the WinHttpConnection class, which also derives from the WinHttpHandle and simplifies the creation of connection objects.

Figure 4 WinHttpConnection Class

class WinHttpConnection : public WinHttpHandle
{
public:
    HRESULT Initialize(PCWSTR serverName,
                       INTERNET_PORT portNumber,
                       const WinHttpSession& session)
    {
        if (!Attach(::WinHttpConnect(session.m_handle,
                                     serverName,
                                     portNumber,
                                     0))) // reserved
        {
            return HRESULT_FROM_WIN32(::GetLastError());
        }

        return S_OK;
    }
};

Request Objects

The request object is where things start to get interesting. A request object is created using the WinHttpOpenRequest function:

HINTERNET request = ::WinHttpOpenRequest(connection,
                                         0, // use GET as the verb
                                         L"/developers/",
                                         0, // use HTTP version 1.1
                                         WINHTTP_NO_REFERRER,
                                         WINHTTP_DEFAULT_ACCEPT_TYPES,
                                         0); // flags

The first parameter specifies the connection to which the request will belong. The second parameter specifies the HTTP verb to use in the request; if this parameter is 0 then a GET request is assumed. The third parameter specifies the name or relative path of the resource that is being requested. The fourth parameter specifies the HTTP protocol version to use; if 0, then HTTP version 1.1 is assumed. The fifth parameter specifies any referring URL. This parameter is usually WINHTTP_NO_REFERRER, indicating no referrer. The second-to-last parameter specifies the media types that you as the client will accept. If this parameter is WINHTTP_DEFAULT_­ACCEPT_TYPES, no types are specified. Servers typically take this to mean that the client only accepts text responses. The last parameter specifies flags that can be used to control the behavior of the request. For example, you might want to specify the WINHTTP_FLAG_SECURE flag for making an SSL request.

Here the synchronous and asynchronous programming models diverge. The synchronous model calls a function to send the request and then calls a function that blocks until a response has been received. The asynchronous model, on the other hand, uses a callback function allowing the remaining function calls to operate asynchronously and thus not block the calling thread.

The callback function is associated with a request object using the WinHttpSetStatusCallback function:

if (WINHTTP_INVALID_STATUS_CALLBACK == ::WinHttpSetStatusCallback(request,
    Callback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
    0)) // reserved
{
    // Call GetLastError for error information
}

The first WinHttpSetStatusCallback parameter specifies the request about whose progress you would like to be notified. The second parameter specifies the address of a callback function. And the third parameter specifies the notifications that you are interested in receiving. WINHTTP_CALLBACK_FLAG_ALL_­NOTIFICATIONS is simply a bitmask that includes all possible notifications.

The callback function should be prototyped as follows:

void CALLBACK Callback(HINTERNET handle,
                       DWORD_PTR context,
                       DWORD code,
                       void* info,
                       DWORD length);

The first parameter provides the handle for the object to which the callback relates. Technically, you can have a callback to handle notifications for session and connection objects, but for the most part this will be a request object. The second parameter is an application-defined value associated with a request when it is sent. The third parameter provides a code indicating the reason for the callback. The last two parameters specify a buffer that may contain any additional information depending on the code provided by the previous parameter. For example, if the WINHTTP_­CALLBACK_STATUS_REQUEST_ERROR notification code is received, then the info parameter points to a WINHTTP_ASYNC_RESULT structure.

A request is sent using the WinHttpSendRequest function:

if (!::WinHttpSendRequest(request,
                          WINHTTP_NO_ADDITIONAL_HEADERS,
                          0, // headers length
                          WINHTTP_NO_REQUEST_DATA,
                          0, // request data length
                          0, // total length
                          0)) // context
{
    // Call GetLastError for error information
}

The first parameter identifies the request to send. The second and third parameters specify a string that may contain any additional headers to include the request. If WINHTTP_NO_ADDITIONAL_­HEADERS is specified, then no additional headers are initially included. As an alternative, you can add headers to the request before it is sent using the WinHttpAddRequestHeaders function. The fourth and fifth parameters specify an optional buffer containing any data to be included as the content, or body, of the request. This is commonly used for POST requests. The second-to-last parameter specifies the total length of the request's content. This value is used to create the Content-Length header included with the request. If it is greater than the length of the data provided by the previous parameters, then additional data may be written to complete the request using the WinHttpWriteData function.

Since I'm using the asynchronous programming model, the function will return without waiting for the request to be sent, and WinHTTP will provide status updates via the callback function passing to it the context value specified in the last parameter to WinHttpSendRequest.

Let's stop for a moment and consider how we can use C++ to bring together what I've covered thus far. In the previous sections, I introduced the rather straightforward WinHttpSession and WinHttpConnection classes for modeling sessions and connections. I need a WinHttpRequest class that not only wraps the various request-related function calls but also provides a simple way to author specific types of requests for an application. Included in that is efficiently managing a buffer that will store both request and response data as it is transferred. Figure 5 provides the outline of the WinHttpRequest class template that I'll build on during the rest of this article. Much of the interesting logic happens inside of the OnCallback member function. However, let me first describe how the WinHttpRequest class template is designed.

Figure 5 WinHttpRequest Class Template

template <typename T>
class WinHttpRequest : public WinHttpHandle
{
public:
    HRESULT Initialize(PCWSTR path,
                       __in_opt PCWSTR verb,
                       const WinHttpConnection& connection)
    {
        HR(m_buffer.Initialize(8 * 1024));

        // Call WinHttpOpenRequest and WinHttpSetStatusCallback.
    }

    HRESULT SendRequest(__in_opt PCWSTR headers,
                        DWORD headersLength,
                        __in_opt const void* optional,
                        DWORD optionalLength,
                        DWORD totalLength)
    {
        T* pT = static_cast<T*>(this);

        // Call WinHttpSendRequest with pT as the context value.
    }

protected:
    static void CALLBACK Callback(HINTERNET handle,
                                  DWORD_PTR context,
                                  DWORD code,
                                  void* info,
                                  DWORD length)
    {
        if (0 != context)
        {
            T* pT = reinterpret_cast<T*>(context);

            HRESULT result = pT->OnCallback(code,
                                            info,
                                            length);

            if (FAILED(result))
            {
                pT->OnResponseComplete(result);
            }
        }
    }

    HRESULT OnCallback(DWORD code,
                       const void* info,
                       DWORD length)
    {
         // Handle notifications here.
    }

    SimpleBuffer m_buffer;
};

Figure 6 lists the DownloadFileRequest class. It derives from WinHttpRequest and uses itself as the template parameter. This is a common technique used by the Visual C++® libraries to implement efficient compile-time virtual function calls. Like the WinHttp­Session and WinHttpConnection classes, WinHttpRequest provides an Initialize member function that initializes the WinHTTP request object. It starts by creating a buffer that will be used for the request and response data. This buffer's implementation isn't important. It just needs to manage the lifetime of a BYTE array. Initialize then calls WinHttpOpenRequest to create the WinHTTP request object and WinHttpSetStatusCallback to associate it with the static Callback member function inside the WinHttpRequest class.

The SendRequest member function just wraps the WinHttpSendRequest function and passes its "this" pointer as the context value for the request. Recall that this value is passed to the callback function and enables it to determine which request object the notification is for. SendRequest uses a static_cast to adjust the "this" pointer to the derived class specified by the template parameter. This is how the compile-time polymorphism is implemented in this case.

Finally, the static Callback member function casts the context value back into a pointer to a request object and calls the On­Callback member function to handle the notification. Since network errors are inevitable, there needs to be some way of handling them naturally. Derived classes implement an OnResponse­Complete member function that takes an HRESULT. If it is S_OK, then the request completed successfully. But if there was some failure in the callback, then an appropriate HRESULT indicates the request has failed.

Before we look at the implementation of the OnCallback member function, take another look at the DownloadFileRequest concrete class in Figure 6 to put WinHttpRequest into perspective. The beauty here is that DownloadFileRequest can focus on the specifics involved in downloading a file. It is not bogged down by the complexities of HTTP request management. Its Initialize member function accepts the path to the source of the file to download as well as a destination stream to which to write it. The OnReadComplete member function will be called as each chunk of the response is received; OnResponseComplete is called when the request is completed.

Figure 6 DownloadFileRequest Class

class DownloadFileRequest : public WinHttpRequest<DownloadFileRequest>
{
public:
    HRESULT Initialize(PCWSTR source,
                       IStream* destination,
                       const WinHttpConnection& connection)
    {
        m_destination = destination;

        return __super::Initialize(source,
                                   0, // GET
                                     connection);
    }

    HRESULT OnReadComplete(const void* buffer,
                           DWORD bytesRead)
    {
        return m_destination->Write(buffer,
                                    bytesRead,
                                    0); // ignored
    }

    void OnResponseComplete(HRESULT result)
    {
        if (S_OK == result)
        {
            // Download succeeded
        }
    }

private:
    CComPtr<IStream> m_destination;
};

Request Notifications

Now I would like to discuss how the OnCallback member function is implemented. As you can see in Figure 7, OnCallback once again uses a static_cast in order to adjust the "this" pointer to point to the derived class. The switch statement is then used to handle various notifications. It will return S_FALSE if there is a particular notification that has not been handled. This may come in handy if you want to override the OnCallback member function in a derived class.

Figure 7 OnCallback Implementation

HRESULT OnCallback(DWORD code,
                   const void* info,
                   DWORD length)
{
    T* pT = static_cast<T*>(this);

    switch (code)
    {
        case <some notification code>:
        {
            // Handle specific notification here.

            break;
        }
        default:
        {
            return S_FALSE;
        }
    }

    return S_OK;
}

The first notification of interest when sending the request, assuming the server is reachable, is WINHTTP_CALLBACK_STATUS_­SENDREQUEST_COMPLETE. This indicates that the WinHttpSendRequest function call completed successfully. Assuming that you're sending a simple GET request, you can now call the WinHttpReceiveResponse function to instruct WinHTTP to start reading the response

case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
{
    if (!::WinHttpReceiveResponse(m_handle,
                                  0)) // reserved
    {
        return HRESULT_FROM_WIN32(::GetLastError());
    }

    break;
}

:

If a response is received, the WINHTTP_CALLBACK_­STATUS_HEADERS_AVAILABLE notification arrives indicating that the response headers are available to read. You can query for various headers that may be relevant to your application. At the very least, you should query for the HTTP status code returned by the server. Header information may be retrieved using the WinHttpQueryHeaders function. A simple implementation might just check for the HTTP_STATUS_OK status code, indicating that the request was successful (see Figure 8).

Figure 8 Check for Successful Request

case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
{
    DWORD statusCode = 0;
    DWORD statusCodeSize = sizeof(DWORD);

    if (!::WinHttpQueryHeaders(m_handle,
                   WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                   WINHTTP_HEADER_NAME_BY_INDEX,
                   &statusCode,
                   &statusCodeSize,
                   WINHTTP_NO_HEADER_INDEX))
    {
        return HRESULT_FROM_WIN32(::GetLastError());
    }

    if (HTTP_STATUS_OK != statusCode)
    {
        return E_FAIL;
    }

    if (!::WinHttpReadData(m_handle,
                   m_buffer.GetData(),
                   m_buffer.GetCount(),
                   0)) // async result
    {
        return HRESULT_FROM_WIN32(::GetLastError());
    }

    break;
}

WinHttpQueryHeaders is a powerful function. Rather than simply returning the text value of a particular named header, it provides a set of information flags that, for the most part, map to headers. But these flags also provide access to other aspects of an HTTP request/response state.

In Figure 8, I'm using the WINHTTP_­QUERY_STATUS_CODE flag in order to retrieve the status code returned by the server. And I'm also using the WINHTTP_QUERY_FLAG_NUMBER modifier to tell WinHttpQueryHeaders that I want the value returned as a 32-bit number rather than as a string.

The third parameter specifies the name of the header to query. If this parameter is WINHTTP_HEADER_NAME_BY_INDEX, the flag passed to the previous parameter identifies the information to return. The next two parameters specify a buffer that will receive the information. The last parameter is useful for reading multiple headers with the same name. If this parameter is WINHTTP_NO_HEADER_INDEX, only the first header's value is returned.

Once you're satisfied that the request was successful, you can call the WinHttpReadData function to begin reading the response. The second and third parameters specify a buffer that will receive the data. In this particular case, I'm using the buffer owned by the WinHttpRequest class template. The last parameter must be zero when using the asynchronous programming model.

When enough response data is received, the WINHTTP_­CALLBACK_STATUS_READ_COMPLETE notification arrives, indicating that data may be available in the buffer. The amount of data read is indicated by OnCallback's length parameter. It can be zero, which would indicate that no more data is available, or it can have any value up to the size of the buffer specified in the WinHttpRead­Data function call (see Figure 9).

Figure 9 Checking Notification

case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
{
    if (0 < length)
    {
        HR(pT->OnReadComplete(m_buffer.GetData(),
                              length));

        if (!::WinHttpReadData(m_handle,
                               m_buffer.GetData(),
                               m_buffer.GetCount(),
                               0)) // async result
        {
            return HRESULT_FROM_WIN32(::GetLastError());
        }
    }
    else
    {
        pT->OnResponseComplete(S_OK);
    }

    break;
}

Now you should be able to see exactly where the "events" in the DownloadFileRequest class in Figure 6 come from. The WINHTTP_CALLBACK_­STATUS_READ_COMPLETE handler calls the OnReadComplete method on the derived class each time data is received. This is followed by another call to WinHttp­ReadData to read the next block of data. Finally, when there is no more data available, it calls the OnResponseComplete method on the derived class with S_OK indicating that the response has been read successfully.

Request Cancellation

WinHTTP provides a relatively error-prone model for asynchronous completion since your application is always notified of an operation's completion via the callback function. Since worker threads are used to execute the callback function, however, canceling a request does require some attention to detail.

A request is canceled simply by closing the request handle using the WinHttpCloseHandle function, but you need to be prepared for subsequent callbacks to arrive after closing the request handle. You can call the WinHttpSetStatusCallback function to indicate that no more callbacks should be made prior to closing the request handle, but WinHTTP does not synchronize this with its worker threads.

You should thus ensure that any shared state is suitably guarded. In particular, any unprotected data that is being shared with the callback should not be freed until the WINHTTP_STATUS_­CALLBACK_HANDLE_CLOSING notification arrives.

In addition, you need to synchronize calls to WinHttpCloseHandle with other functions that may be operating on the request object such as WinHttpReadData, WinHttpWriteData, and WinHttpSendRequest. Request cancellation is one of the more problematic areas of asynchronous WinHTTP so be sure to carefully review your code as it relates to cancellation.

Sending Request Data

What about POST requests? Apart from using a different HTTP verb, such requests also have additional data that is included as content within the body of the request. Figure 10 illustrates an updated WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE handler, which can easily handle both GET and POST requests.

Figure 10 Handling GET and POST Requests

case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
{
    HRESULT result = pT->OnWriteData();

    if (FAILED(result))
    {
        return result;
    }

    if (S_FALSE == result)
    {
        if (!::WinHttpReceiveResponse(m_handle,
                                      0)) // reserved
        {
            return HRESULT_FROM_WIN32(::GetLastError());
        }
    }

    break;
}

When the WINHTTP_CALLBACK_STATUS_SENDREQUEST_­COMPLETE notification arrives, it gives the derived class an opportunity to write data as part of the request by calling its OnWriteData member function. The WinHttpRequest class template provides a default implementation for GET requests that simply returns S_FALSE. Data can be written using the WinHttpWriteData function:

if (!::WinHttpWriteData(request,
                       buffer,
                       count,
                       0)) // async result
{
    // Call GetLastError for error information
}

The second and third parameters specify a buffer containing the data to write. Once the data has been written, the WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE notification arrives. The OnWriteData member function on the derived class is once again called to write the next block of data. Once it has completed, it returns S_FALSE and the base class handler proceeds to call WinHttpReceiveResponse to receive the response as usual.

What's Next

Of course, there's a lot more that I could say about writing HTTP client applications with C++, but unfortunately I'm out of space here. However, I hope this detailed introduction has given you enough information to get you started.

As you can see, WinHTTP provides a modern and powerful API for writing HTTP client applications. Over and above all of the functionality I've described in this article, it also provides a few other important facilities including support for a variety of authentication schemes as well as supplying and validating certificates for SSL. Furthermore, it also provides functions for building and parsing URLs. If you are looking for a rich platform for building efficient and scalable HTTP client applications, WinHTTP may be just what you need.

Determining Proxy Settings

One of the challenges in writing a reliable HTTP client application is the lack of a consistent way to retrieve proxy settings. What happens when the server cannot be reached? How do you determine which proxy server to use? Although there isn't a universal standard in place, with a little effort you can usually figure out which proxy server to use. Unfortunately, many applications instead simply prompt the user for the name of a proxy server. But I think that software should be smart enough not to require the user to supply information that software can usually figure out.

There are a few places to look for proxy settings. I'll start with the most direct approach. In my discussion on Session objects I introduced the WinHttpOpen function for creating a new session object:

HINTERNET session = ::WinHttpOpen(0, // no agent string
                                  WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                                  WINHTTP_NO_PROXY_NAME,
                                  WINHTTP_NO_PROXY_BYPASS,
                                  WINHTTP_FLAG_ASYNC);
                                  

It had a few parameters for proxy settings that I skimmed over initially. Here I'll describe what they do. The second parameter specifies the access type. Figure A lists the values that may be used.

Figure A Session Access Types

Session Object Parameters Description
WINHTTP_ACCESS_TYPE_NO_PROXY Don't use a proxy server by default
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY Use WinHTTP proxy settings stored in the registry.
WINHTTP_ACCESS_TYPE_NAMED_PROXY Use the specified proxy settings.

WINHTTP_ACCESS_TYPE_NO_PROXY indicates that proxy settings should not be retrieved or used. You can override this after the session has been created and I'll describe how to do that in a moment.

WINHTTP_ACCESS_TYPE_DEFAULT_PROXY indicates that any proxy settings stored by WinHTTP in the registry should be used. This is useful in allowing administrators to control the proxy settings. It is also independent of Internet Explorer's proxy settings and is thus ideal for server scenarios. The proxycfg.exe utility may be used on Windows XP and Windows Server 2003 to set and query the WinHTTP proxy settings. The proxycfg.exe utility has been replaced on Windows Vista and Windows Server 2008 and the functionality is now provided by the netsh.exe utility. You can also set the WinHTTP proxy settings directly using the WinHttpSetDefaultProxyConfiguration function and query it using the WinHttpGetDefaultProxyConfiguration function. These settings persist across reboots and are shared by all logon sessions. Both functions use the WINHTTP_PROXY_INFO structure which maps directly to the three proxy-related parameters of the WinHttpOpen function.

WINHTTP_ACCESS_TYPE_NAMED_PROXY indicates that WinHTTP should use the proxy settings in the next two parameters. WinHttpOpen's third parameter specifies the name of a proxy server and the fourth parameter specifies an optional list of HTTP servers that should not be routed through the proxy server specified in the previous parameter.

If you're writing a Windows service or server application then this may be all that you need. On the other hand, end users don't typically know how or want to configure proxy settings so a little more effort may be required to make it as seamless as possible. Another approach is to retrieve Internet Explorer's proxy settings since most users will have their browsers already configured to access the Web through a Web proxy. You can find the Internet Explorer dialog for configuring proxy settings by clicking the "LAN settings" button on the Connections tab of the Internet Explorer Options window. As this is a common requirement, WinHTTP provides the WinHttpGetIEProxyConfigForCurrentUser function for directly retrieving these very Internet Explorer proxy settings for the current user:

WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig = { 0 };

if (!::WinHttpGetIEProxyConfigForCurrentUser(&ieProxyConfig))
{
    // Call GetLastError for error information.
}
Here's what WINHTTP_CURRENT_USER_IE_PROXY_CONFIG looks like:
struct WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
{
    BOOL    fAutoDetect;
    LPWSTR  lpszAutoConfigUrl;
    LPWSTR  lpszProxy;
    LPWSTR  lpszProxyBypass;
};

The non-null string member variables must be freed using the GlobalFree function. The fAutoDetect member maps to the "Automatically detect settings" option in the LAN settings dialog. The lpszAutoConfigUrl member maps to the "Use automatic configuration script" address and is only populated if this option is checked. The lpszProxy member maps to the "Use a proxy server..." address and port and is only populated if this option is checked. Finally, lpszProxyBypass maps to the list of servers specified in the Exceptions area of the window that appears when you click the Advanced button and is only populated if the "Bypass proxy server for local addresses" option is checked.

If lpszProxy is populated you're in luck as you can simply use it as the proxy server. Figuring out what to do in the case of the other options takes a bit more work.

If lpszAutoConfigUrl is populated then it means that Internet Explorer downloads a Proxy Auto-Configuration (PAC) file to determine the proxy server for a particular connection. Reproducing this behavior can be challenging since the downloaded PAC file often contains JavaScript that may be used to direct the client to a particular proxy server depending on the destination server. Fortunately WinHTTP provides the WinHttpGetProxyForUrl function that abstracts this for you:

WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions = { 0 };
autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
autoProxyOptions.lpszAutoConfigUrl = ieProxyConfig.lpszAutoConfigUrl;

WINHTTP_PROXY_INFO proxyInfo = { 0 };

if (!::WinHttpGetProxyForUrl(session,
                             L"https://example.com/path",
                             &autoProxyOptions,
                             &proxyInfo))
{
    // Call GetLastError for error information.
}

The non-null string member variables of the WINHTTP_PROXY_INFO structure must also be freed using the GlobalFree function. The first WinHttpGetProxyForUrl parameter specifies the WinHTTP session handle. The second parameter specifies the URL of the request you would like to send. The third parameter is an input parameter and specifies a WINHTTP_AUTOPROXY_OPTIONS structure which controls the behavior of the WinHttpGetProxyForUrl function. This is where you specify the URL of the PAC file retrieved from Internet Explorer proxy settings. The last parameter is an out parameter and specifies the WINHTTP_PROXY_INFO structure that receives the results. The results can then be used with the WinHttpSetOption function along with the WINHTTP_OPTION_PROXY option to set the proxy settings for a particular request object.

Finally, if WINHTTP_CURRENT_USER_IE_PROXY_CONFIG's fAutoDetect member variable is true it means that Internet Explorer uses the Web Proxy Auto-Discovery (WPAD) protocol to discover proxy settings. WPAD relies on updates to the network's DNS or DHCP servers to provide the URL of a PAC script so that clients don't have to be preconfigured with a specific PAC script. This is analogous to the relationship between static and dynamic IP addresses. You can instruct WinHttpGetProxyForUrl to use WPAD to discover the URL of the PAC file automatically by changing the WINHTTP_AUTOPROXY_OPTIONS initialization as follows:

WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions = { 0 };
autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP 
  | WINHTTP_AUTO_DETECT_TYPE_DNS_A;

Naturally you can use the WinHTTP "AutoProxy" capabilities without a dependency on Internet Explorer. Using Internet Explorer settings is mainly a stopgap until WPAD becomes more ubiquitous in corporate network environments. If WPAD is not available on your network then WinHttpGetProxyForUrl may take a few seconds to return and GetLastError will return ERROR_WINHTTP_AUTODETECTION_FAILED.

Send your questions and comments to mmwincpp@microsoft.com.

Kenny Kerr is a software craftsman specializing in software development for Windows. He has a passion for writing and teaching developers about programming and software design. Reach Kenny at weblogs.asp.net/kennykerr.