WinInet is a higher-level API than Winsock, but it works only for HTTP, FTP, and gopher client programs in both asynchronous and synchronous modes. You can't use it to build servers. The WININET DLL is independent of the WINSOCK32 DLL. Microsoft Internet Explorer 3.0 (IE3) uses WinInet, and so do ActiveX controls.
WinInet far surpasses Winsock in the support it gives to a professional-level client program. Following are just some of the WinInet benefits:
WinInet is a modern API available only for Win32. The MFC wrapping is quite good, which means we didn't have to write our own WinInet class library. Yes, MFC WinInet supports blocking calls in multithreaded programs, and by now you know that makes us happy.
The MFC classes closely mirror the underlying WinInet architecture, and they add exception processing. These classes are summarized in the sections on the following pages.
You need only one CInternetSession object for each thread that accesses the Internet. After you have your CInternetSession object, you can establish HTTP, FTP, or gopher connections or you can open remote files directly by calling the OpenURL member function. You can use the CInternetSession class directly, or you can derive a class from it in order to support status callback functions.
The CInternetSession constructor calls the WinInet InternetOpen function, which returns an HINTERNET session handle that is stored inside the CInternetSession object. This function initializes your application's use of the Win- Inet library, and the session handle is used internally as a parameter for other WinInet calls.
An object of class CHttpConnection represents a "permanent" HTTP connection to a particular host. You know already that HTTP doesn't support permanent connections and that FTP doesn't either. (The connections last only for the duration of a file transfer.) WinInet gives the appearance of a permanent connection because it remembers the host name.
After you have your CInternetSession object, you call the GetHttpConnection member function, which returns a pointer to a CHttpConnection object. (Don't forget to delete this object when you are finished with it.)
The GetHttpConnection member function calls the WinInet InternetConnect function, which returns an HINTERNET connection handle that is stored inside the CHttpConnection object and used for subsequent WinInet calls.
These classes are similar to CHttpConnection, but they use the FTP and gopher protocols. The CFtpConnection member functions GetFile and PutFile allow you to transfer files directly to and from your disk.
With HTTP, FTP, or gopher, your client program reads and writes byte streams. The MFC WinInet classes make these byte streams look like ordinary files. If you look at the class hierarchy, you'll see that CInternetFile is derived from CStdioFile, which is derived from CFile. Therefore, CInternetFile and its derived classes override familiar CFile functions such as Read and Write. For FTP files, you use CInternetFile objects directly, but for HTTP and gopher files, you use objects of the derived classes CHttpFile and CGopherFile. You don't construct a CInternetFile object directly, but you call CFtpConnection::OpenFile to get a CInternetFile pointer.
If you have an ordinary CFile object, it has a 32-bit HANDLE data member that represents the underlying disk file. A CInternetFile object uses the same m_hFile data member, but that data member holds a 32-bit Internet file handle of type HINTERNET, which is not interchangeable with a HANDLE. The CInternetFile overridden member functions use this handle to call WinInet functions such as InternetReadFile and InternetWriteFile.
This Internet file class has member functions that are unique to HTTP files, such as AddRequestHeaders, SendRequest, and GetFileURL. You don't construct a CHttpFile object directly, but you call the CHttpConnection::OpenRequest function, which calls the WinInet function HttpOpenRequest and returns a CHttpFile pointer. You can specify a GET or POST request for this call.
Once you have your CHttpFile pointer, you call the CHttpFile::SendRequest member function, which actually sends the request to the server. Then you call Read.
These classes let your client program explore FTP and gopher directories.
The MFC WinInet classes throw CInternetException objects that your program can process with try/catch logic.
WinInet and MFC provide callback notifications as a WinInet operation progresses, and these status callbacks are available in both synchronous (blocking) and asynchronous modes. In synchronous mode (which we're using exclusively here), your WinInet calls block even though you have status callbacks enabled.
Callbacks are easy in C++. You simply derive a class and override selected virtual functions. The base class for WinInet is CInternetSession. Now let's derive a class named CCallbackInternetSession:
class CCallbackInternetSession : public CInternetSession { public: CCallbackInternetSession( LPCTSTR pstrAgent = NULL, DWORD dwContext = 1, DWORD dwAccessType = PRE_CONFIG_INTERNET_ACCESS, LPCTSTR pstrProxyName = NULL, LPCTSTR pstrProxyBypass = NULL, DWORD dwFlags = 0 ) { EnableStatusCallback() } protected: virtual void OnStatusCallback(DWORD dwContext, DWORD dwInternalStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); };
The only coding that's necessary is a constructor and a single overridden function, OnStatusCallback. The constructor calls CInternetSession::EnableStatusCallback to enable the status callback feature. Your WinInet client program makes its various Internet blocking calls, and when the status changes, OnStatusCallback is called. Your overridden function quickly updates the UI and returns, and then the Internet operation continues. For HTTP, most of the callbacks originate in the CHttpFile::SendRequest function.
What kind of events trigger callbacks? A list of the codes passed in the dwInternalStatus parameter is shown here.
Code Passed | Action Taken |
INTERNET_STATUS_RESOLVING_NAME | Looking up the IP address of the supplied name. The name is now in lpvStatusInformation. |
INTERNET_STATUS_NAME_RESOLVED | Successfully found the IP address. The IP address is now in lpvStatusInformation. |
INTERNET_STATUS_CONNECTING_TO_SERVER | Connecting to the socket. |
INTERNET_STATUS_CONNECTED_TO_SERVER | Successfully connected to the socket. |
INTERNET_STATUS_SENDING_REQUEST | Send the information request to the server. |
INTERNET_STATUS_REQUEST_SENT | Successfully sent the information request to the server. |
INTERNET_STATUS_RECEIVING_RESPONSE | Waiting for the server to respond to a request. |
INTERNET_STATUS_RESPONSE_RECEIVED | Successfully received a response from the server. |
INTERNET_STATUS_CLOSING_CONNECTION | Closing the connection to the server. |
INTERNET_STATUS_CONNECTION_CLOSED | Successfully closed the connection to the server. |
INTERNET_STATUS_HANDLE_CREATED | Program can now close the handle. |
INTERNET_STATUS_HANDLE_CLOSING | Successfully terminated this handle value. |
INTERNET_STATUS_REQUEST_COMPLETE | Successfully completed the asynchronous operation. |
You can use your status callback function to interrupt a WinInet operation. You could, for example, test for an event set by the main thread when the user cancels the operation.
And now for the WinInet equivalent of our Winsock client program that implements a blind GET request. Because you're using WinInet in blocking mode, you must put the code in a worker thread. That thread is started from a command handler in the main thread:
AfxBeginThread(ClientWinInetThreadProc, GetSafeHwnd());
Here's the client thread code:
CString g_strServerName = "localhost"; // or some other host name UINT ClientWinInetThreadProc(LPVOID pParam) { CInternetSession session; CHttpConnection* pConnection = NULL; CHttpFile* pFile1 = NULL; char* buffer = new char[MAXBUF]; UINT nBytesRead = 0; try { pConnection = session.GetHttpConnection(g_strServerName, 80); pFile1 = pConnection->OpenRequest(1, "/"); // blind GET pFile1->SendRequest(); nBytesRead = pFile1->Read(buffer, MAXBUF - 1); buffer[nBytesRead] = `\0'; // necessary for message box char temp[10]; if(pFile1->Read(temp, 10) != 0) { // makes caching work if read complete AfxMessageBox("File overran buffer not cached"); } AfxMessageBox(buffer); } catch(CInternetException* e) { // Log the exception e->Delete(); } if(pFile1) delete pFile1; if(pConnection) delete pConnection; delete [] buffer; return 0; }
The second Read call needs some explanation. It has two purposes. If the first Read doesn't read the whole file, that means that it was longer than MAXBUF -1. The second Read will get some bytes, and that lets you detect the overflow problem. If the first Read reads the whole file, you still need the second Read to force WinInet to cache the file on your hard disk. Remember that WinInet tries to read all the bytes you ask it tothrough the end of the file. Even so, you need to read 0 bytes after that.