Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By Mickey Williams and David Bennett

MFC WinInet Classes

In addition to the Win32 Internet Extension C API that you saw previously, MFC provides the WinInet classes that encapsulate this functionality into a set of classes that you can use to easily add Internet functionality to your C++ applications.

These classes add some additional functionality to the WinInet C API, including buffered I/O, default parameters, exception handling, and automatic cleanup of handles and connections.

Figure 11.1 shows the relationship of the classes that make up the MFC WinInet classes.

11fig01.gif

Figure 11.1 MFC WinInet classes.

CInternetSession

Any application that is going to use the WinInet classes first needs to have a CInternetSession object. This object is used to create and initialize an Internet session and may be used to handle a connection to a proxy server. Often, if your application will maintain an Internet session for the duration of the application, it is most convenient to make your CInternetSession object a member of your CWinApp object.

Connection Classes

For many Internet operations, you also need to open a connection to a server. MFC uses the CInternetConnection class to represent a connection to a server, as well as a class derived from CInternetConnection for each specific protocol. These include CFtpConnection, CGopherConnection, and CHttpConnection. These objects are created by member functions of CInternetSession.

File Classes

To help your application work with the files used in various Internet protocols, MFC provides several new Internet file classes derived from CInternetFile. These include CInternetFile itself for FTP files, CHttpFile for HTTP files, and CGopherFile for Gopher files. As you will see, these objects are returned from CInternetSession::OpenURL() or the protocol-specific function CFtpConnection::OpenFile(), CGopherConnection::OpenFile(), or CHttpConnection::OpenRequest().

CInternetException

Many of the member functions for the WinInet classes may throw an exception in the event of errors. In most cases, a CInternetException is thrown. For more on handling these exceptions, see Chapter 14, "Error Detection and Exception Handling Techniques." In your applications, you can generate a CInternetException with a call to ::AfxThrowInternetException().

Using CInternetSession

The CInternetSession class provides a context for all the other operations that will be performed using the WinInet classes. In this section, you'll look at how to create a CInternetSession and how to use it to perform simple file retrieval. You'll also see how the WinInet classes enable you to handle asynchronous operations.

Creating a Session

To establish an Internet session in MFC, you need only construct a new CInternetSession object—although you may want to derive your own class from CInternetSession, as you will see in the discussion about asynchronous I/O. The constructor for CInternetSession takes a fair number of parameters, although you will notice that all these have reasonable default values provided. The following is the constructor for CInternetSession:

CInternetSession( LPCTSTR pstrAgent = NULL, DWORD dwContext = 1,
    DWORD dwAccessType = PRE_CONFIG_INTERNET_ACCESS,
    LPCTSTR pstrProxyName = NULL, LPCTSTR pstrProxyBypass = NULL,
    DWORD dwFlags = 0 );

You can use the pstrAgent parameter to specify the name of your application, which may be used, in turn, to identify your application to servers for certain protocols. By default, this is NULL—in which case, MFC will get the application name with a call to AfxGetAppName().

dwContext specifies a context value that will be used for asynchronous operations on this CInternetSession and any objects created by it.

The dwAccessType parameter tells MFC how to connect to the network. You can use INTERNET_OPEN_TYPE_DIRECT to connect directly, or INTERNET_OPEN_TYPE_PROXY to connect through a proxy server. The default, INTERNET_OPEN_TYPE_PRECONFIG, will access the network as configured in the Registry.

If you are using proxy access, you also need to specify the name of the proxy server in pstrProxyName. In addition, you may specify a list of server addresses that should be accessed directly, without using the proxy server, in pstrProxyBypass.

The dwFlags parameter enables you to specify several options for how your Internet session will behave. You can specify INTERNET_FLAG_DONT_CACHE to tell the framework not to cache data, or you may specify INTERNET_FLAG_OFFLINE to tell the framework to access only data that is currently in the cache—attempts to access data not in the cache will return an error. In addition, you may specify the INTERNET_FLAG_ASYNC flag to enable asynchronous operations for the session.

Retrieving a File

After an Internet session is established, you can begin retrieving files with calls to CInternetSession::OpenURL(). Note that you do not need to establish a connection explicitly before using this function; it will create its own session if necessary.

OpenURL() uses the InternetOpenURL() Win32 API function and has the same limitations: You can only retrieve data, and you cannot manipulate data on the server. For operations requiring this sort of interaction, you need to open a connection, as you will see later.

OpenURL() takes a URL string and returns a pointer to an object of a type appropriate for the file specified in the URL. Table 11.1 lists the types of URLs currently supported, with the types of pointers returned by OpenURL().

Table 11.1. URL Types and Associated File Classes

URL Type OpenURL() Return Type
http:\\ CHttpFile*
ftp:// CFtpFile*
gopher:\\ CGopherFile*
file:// CStdioFile*

Note that the CStdioFile object may be used just like any other CStdioFile, as shown in Chapter 14.

The following is the prototype for CInternetSession::OpenURL():

CStdioFile* OpenURL( LPCTSTR pstrURL, DWORD dwContext = 1, DWORD dwFlags = 0,
    LPCTSTR pstrHeaders = NULL, DWORD dwHeadersLength = 0 );

The first parameter, pstrURL , points to the URL string for the file to retrieve. The dwContext parameter is used for asynchronous operations, as you will see soon.

OpenURL() also supports several flags, as passed in dwFlags, that you can use to alter the behavior of OpenURL(). INTERNET_FLAG_RELOAD forces the framework to reload the file from the network, even if it is found in the cache. INTERNET_FLAG_DONT_CACHE prevents the framework from saving the file in the cache.

The INTERNET_OPEN_FLAG_USE_EXISTING_CONNECT flag tells the framework to try to use an existing connection to retrieve the file, rather than opening a new connection for each call to OpenURL().

For FTP applications, you also can specify INTERNET_FLAG_PASSIVE to use passive FTP semantics.

Applications requesting files using HTTP may specify the INTERNET_FLAG_SECURE flag to use secure transactions, either with Secure Sockets Layer (SSL) or PCT. In addition, HTTP requests may include additional RFC822, MIME, or HTTP headers, as specified with pstrHeaders and dwHeadersLength.

The following example shows how an application can create an Internet session and use OpenURL() to retrieve a file using HTTP:

#include <AfxInet.h>
    // Create Session Object
    CInternetSession mySession;

    CHttpFile *pHttpFile;
    CString tmpStr;
    char inBuf[10000];
    UINT nBytesRead;
    try
    {
        // Open HTTP file
        pHttpFile =(CHttpFile *) mySession.OpenURL("http://www.racotek.com");
    }
    catch (CInternetException)
    {
        AfxMessageBox("Received Exception from OpenURL()");
        // Handle exception
    }
    if(pHttpFile == NULL)
        AfxMessageBox("Error in OpenURL");
    else
    {
        // Read from file
        nBytesRead = pFile->Read(inBuf, sizeof(inBuf));
        tmpStr.Format("Read %d bytes", nBytesRead);
        AfxMessageBox(tmpStr);
    }

Note that, although this example shows how you can handle the CInternetException that may be thrown by OpenURL(), it doesn't do any real error handling. In addition, your application probably will want to use a more robust and efficient buffer mechanism than the simple large array I have used here.

OpenURL() can be used in a similar fashion for other protocols, such as Gopher, FTP, or local files. Just make sure to use the appropriate file object (CGopherFile, CInternetFile, or CStdioFile).

If you are unsure of the type of service for an HINTERNET, you can use CInternetSession::ServiceTypeFromHandle() to determine whether the handle is to be used with FTP, HTTP, Gopher, or local files.

Establishing a Connection

For applications that need to perform more interactive operations with a server, you need to establish a connection. In MFC, a connection is represented by a class derived from CInternetConnection, including CFtpConnection, CHttpConnection, and CGopherConnection. You can create a new connection object by calling one of the Get-connection methods of class CInternetSession. These include GetFtpConnection(), GetHttpConnection(), and GetGopherConnection(). You will see examples of each of these in the next sections.

Asynchronous Operations with CInternetSession

The MFC WinInet classes enable you to take advantage of the asynchronous mechanism that the WinInet C API provides for monitoring the status of operations in progress. The callback function you saw earlier in the C API is implemented as an overrideable member function of CInternetSession. Thus, if you want to use the WinInet classes asynchronously, you first must derive your own class from CInternetSession, overriding the OnStatusCallback() member function. Here is the prototype for this callback function:

virtual void OnStatusCallback( DWORD dwContext, DWORD dwInternetStatus,
    LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);

When this function is called, it receives the context value for the operation that generated the call in dwContext. For some functions, such as CInternetSession::GetFtpConnection(), this is the context value that was passed to the object's constructor; other functions, such as CInternetSession::OpenURL(), enable you to specify a context value for that operation.

The reason why the callback function was called is passed in dwInternetStatus, and additional information may be supplied in lpvStatusInformation and dwStatusInformationLength. The value of dwInternetStatus and the information passed in lpvStatusInformation is the same as that returned by the C API callback function you saw earlier.

To use asynchronous operations with an object of your new CInternetSession class, you must pass a value of INTERNET_FLAG_ASYNC in the dwFlags parameter of the constructor for your new class. In addition, you must specify a non-zero context value for the operations that you want to complete asynchronously.

The last step in enabling asynchronous operations is a call to CInternetSession::EnableStatusCallback(), which takes a single BOOL parameter. If this is TRUE (the default), the callback function will be enabled. You can disable asynchronous operation by calling EnableStatusCallback() with a parameter of FALSE.

After asynchronous operation is enabled, any calls that are made with a non-zero context value may complete asynchronously—the function will return "failure" (FALSE or NULL), and a subsequent call to GetLastError() will return ERROR_IO_PENDING. Various events in the processing of the request then will generate calls to OnStatusCallback(). When the operation is complete, OnStatusCallback() is called with a status of INTERNET_ STATUS_REQUEST_COMPLETE.

Other CInternetSession Members

The CInternetSession class encapsulates the session handle used earlier in the WinInet C API. As with the session handle, you can manipulate various options for the CInternetSession with the QueryOption() and SetOption() member functions, which are very similar to the C API functions ::InternetQueryOption() and ::InternetSetOption().

You can use the GetContext() member of CInternetSession to retrieve the context value that was assigned to the session.

CInternetSession::Close() should be called when you are finished with a session.

You also can retrieve the actual HINTERNET handle for the session by using the HINTERNET operator provided by the CInternetSession class.

Working with FTP

To establish an FTP connection, you can use CInternetSession::GetFtpSession(), which returns a pointer to a new CFtpConnection object. GetFtpSession() enables you to specify the server and port to connect to, the username and password to use, and a flag for active or passive mode. However, you can use the default for many of these parameters.

After you have established the FTP connection, most of the operations your application will need to perform are accessed by way of the member functions of CFtpConnection. When you are finished with the connection, you should call CFtpConnection::Close() to close the connection.

FTP Directories

CFtpConnection enables you to work with the current directory on the FTP server by way of its GetCurrentDirectory() and SetCurrentDirectory() member functions. In addition, you can retrieve the current server directory as a URL by calling CFtpConnection::GetCurrentDirectoryAsURL().

You also can create a directory on the server with CFtpConnection::CreateDirectory() or remove a directory with CFtpConnectionRemoveDirectory().

Finding Files

To help locate files on an FTP server, MFC provides the CFtpFileFind class, which is derived from CFileFind. To create a new CFtpFileFind object, you first must create a valid CFtpConnection. The constructor for CFtpFileFind then takes a pointer to the CFtpConnection and an optional context value.

After you have created the CFtpFileFind object, you can use its member functions to search for files. To begin a search, you can call CFtpFileFind::FindFile(), which takes parameters for the filename to find and a set of flags. You may use the flags to specify how the find is to operate. For example, you may specify INTERNET_FLAG_RELOAD to force the find to get the most current data from the server, rather than using cached data. You may specify a NULL pointer to the filename (the default) to browse all files in the current directory.

To find the next file that matches the search string, you can use CFtpFileFind::FindNextFile(). For example, if you specified a NULL pointer to the filename in FindFile(), you can walk through the whole directory by making calls to FindNextFile() until it returns FALSE. If you have read the entire directory, a call to GetLastError() will return ERROR_NO_MORE_FILES; other return values indicate a more substantial error.

After a successful call to FindFile() or FindNextFile(), you can call CFtpFileFind::GetFileURL() to return a CString that contains the URL for the found file.

In addition, you can use the member functions of CFindFile, from which CFtpFindFile is derived, to get information about the current file. CFindFile implements methods such as GetLength() and GetFileName() to retrieve information about the file, as well as many others that can be used to retrieve information about a file's attributes.

FTP Files

You can copy a file from the FTP server to the local machine with CFtpConnection::GetFile(). This function enables you to specify the local and remote filenames, as well as several other parameters to specify the attributes of the new file, what to do if this file already exists, a context value for asynchronous I/O, and the transfer mode. Fortunately, the default transfer mode is FTP_TRANSFER_TYPE_BINARY, rather than the default for many command-line FTP utilities; thus, you have to work harder if you want to mangle your .exe files by adding carriage returns.

Similarly, the CFtpConnection::PutFile() method enables you to transfer a file from the local machine to the server. However, PutFile() enables you to specify only the two filenames: transfer mode and context value. The attributes of the new file are left up to the FTP server.

You also can open a file in place on the server using the CFtpConnection::OpenFile() method, which returns a pointer to a CInternetFile object. You then may use the CInternetFile::Read() or CInternetFile::Write() method to read from or write to the file.

OpenFile() is useful for instances when your application wants to create a file on the server from data in memory rather than from a local disk file, or when you want to read a file directly into memory rather than into a disk file. OpenFile() also may be useful in cases where you want to be able to control the progress of a file transfer more closely—for example, to display the status to the user.

CFtpConnection also provides the Remove() method for removing files from the FTP server and the Rename() method for renaming files on the server.

Working with HTTP

In this section, you will explore the MFC WinInet classes that are used for working with HTTP, starting with the CHttpConnection class and including the CHttpFile class.

HTTP Connections

MFC provides the CHttpConnection class to represent connections to an HTTP server. A new connection is established by calling CInternetSession::GetHttpConnection(), which enables you to specify the server and port to connect to, as well as a username and password. In many cases, you need only specify the server name—the defaults for the other parameters usually are sufficient. The following is the prototype for GetHttpConnection():

CHttpConnection* GetHttpConnection( LPCTSTR pstrServer,
    INTERNET_PORT nPort = INTERNET_INVALID_PORT_NUMBER,
    LPCTSTR pstrUserName = NULL, LPCTSTR pstrPassword = NULL );

HTTP Files

To work with files from an HTTP server, you will be working with CHttpFile objects. As you saw earlier, you can create a CHttpFile object with CInternetSession::OpenURL(), although here you will see how you can implement greater functionality by creating a CHttpFile object from a connection you have explicitly created with a call to CHttpConnection::OpenRequest(). You also will take a look at the various member functions of CHttpFile you can use to build requests, send them to the server, and retrieve their results.

Creating a New CHttpFile

After you have created a CHttpConnection with CInternetSession::GetHttpConnection(), you can create a CHttpFile from this connection by calling CHttpConnection::OpenRequest():

CHttpFile* OpenRequest( LPCTSTR pstrVerb, LPCTSTR pstrObjectName,
    LPCTSTR pstrReferer = NULL, DWORD dwContext = 1,
    LPCTSTR* pstrAcceptTypes = NULL, LPCTSTR pstrVersion = NULL,
    DWORD dwFlags = INTERNET_FLAG_EXISTING_CONNECT );

The pstrVerb parameter specifies the verb to use for the request. If this pointer is NULL, GET is used. In addition, you may specify the verb as an int, using one of the following constants:

  • HTTP_VERB_DELETE
  • HTTP_VERB_GET
  • HTTP_VERB_HEAD
  • HTTP_VERB_LINK
  • HTTP_VERB_POST
  • HTTP_VERB_PUT
  • HTTP_VERB_UNLINK

pstrObjectName points to a string containing the target object of the specified verb. In most cases, this is a filename, executable module, or search specifier. You can parse the object name, as well as several other elements from a URL, with ::AfxParseURL().

The additional parameters enable you to specify the referring page, the type of data to accept, the version of HTTP to use, a context value, and a set of flags, which give options for things such as cache usage.

Adding HTTP Headers

You can add additional headers to the request contained in the CHttpFile by calling CHttpFile::AddRequestHeader(), which enables you to specify a header string as either a CString or a C string and its length, and a set of option flags for the header operation. You saw the possible flag values in the discussion of the ::HttpAddRequestHeaders() function.

Sending the Request

After you have added all the desired headers to the request, it is forwarded to the server with a call to CHttpFile::SendRequest(), which enables you to specify additional headers to send (as a CString or character array), as well as any optional data that will be sent for operations such as POST or PUT.

After the request has been sent successfully, you can call CHttpFile::QueryInfoStatusCode() to return the status of the request by updating a DWORD at an address you pass. The status may have many different specific values, but these are grouped into the following ranges:

  • 200-299: Success
  • 300-399: Information
  • 400-499: Request Error
  • 500-599: Server Error

If the request has completed successfully, you can read the data returned from the server by using the Read() method of CHttpFile.

Working with Gopher

In this section, you will look at the MFC WinInet classes that are used to work with Gopher servers, including CGopherConnection, CGopherLocator, CGopherFileFind, and CGopherFile.

Gopher Connections

A connection to a Gopher server is represented by the CGopherConnection class. You create a new CGopherConnection by calling CInternetSession::GetGopherConnection():

CGopherConnection* GetGopherConnection( LPCTSTR pstrServer,
    LPCTSTR pstrUserName = NULL, LPCTSTR pstrPassword = NULL,
    INTERNET_PORT nPort = INTERNET_INVALID_PORT_NUMBER);

This function is passed the name of the Gopher server and optional username and password strings. You also can specify a port on the Gopher server if the server uses a port other than the default.

Gopher Locators

Like the URLs used for locating HTTP files, the Gopher protocol uses locator strings to access files. In the MFC WinInet classes, the locator string is encapsulated by objects of class CGopherLocator, which are created by calls to CGopherConnection::CreateLocator(). This enables you to create a CGopherLocator from the individual components of the locator string or from a complete locator string. The locator object then can be used in calls to CGopherFileFind::FindFile() and CGopherConnection::OpenFile().

Finding Gopher Files

The MFC WinInet classes include the CGopherFileFind class to help you find files on a Gopher server. Unlike many of the other WinInet classes, a new CGopherFileFind object is not created by a member function of CInternetSession or a connection class; however, you must pass a pointer to a valid CGopherConnection object in the constructor for CGopherFileFind:

CGopherFileFind( CGopherConnection* pConnection, DWORD dwContext = 1 );

To begin a search of a Gopher server, you use the CGopherFileFind::FindFile() function, which enables you to begin a search for a particular filename string. There are two versions of FindFile(). The first allows a reference to a CGopherLocator parameter, which will receive the results of the first file found. The second version requires that you use CGopherFileFind::GetLocator() to retrieve the results.

Regardless of which version of FindFile() you used to begin the query, you can retrieve additional results by calling CGopherFileFind::FindNextFile() to find the next file and CGopherFileFind::GetLocator() to retrieve the locator for the found file. When no more files are found, FindNextFile() returns FALSE and GetLastError() returns ERROR_NO_MORE_FILES.

Working with Gopher Files

To retrieve data from a Gopher server, you use the CGopherFile class. CGopherFile objects are created by a call to CGopherConnection::OpenFile(), which takes a reference to a CGopherLocator as a parameter. The CGopherLocator, which specifies the file to open, is returned by CGopherConnection::CreateLocator() or CGopherFileFind::GetLocator().

If CGopherConnection::OpenFile() completes successfully, you can use the returned CGopherFile to read the data in the file by calling CGopherFile::Read(). You also can use the other member functions of CInternetFile (the base class for CGopherFile) to work with the file, although the Gopher protocol does not allow you to write data to the server.

+ Share This