In my search to download files using HTTP, I have encountered many good implementations and examples but none met my criteria - I began implementing my own. I needed fetching a file via HTTP and writing this file to disk, displaying the various HTTP codes, in-order to react or display some sort of message to the user. The best solution I found was to derive a class from
CInternetSession which allowed me status callbacks and plenty of room to add more functionality to the class as I needed. I'll attempt to walk through this showing how to download files via HTTP.
First, we must create an HTTP session using the
CInternetSession class as follows.
CInternetSession session(pstrAgent, dwContext, dwAccessType,
pstrProxyName, pstrProxyBypass, dwFlags);
The first parameter is a pointer to the string referring to the application name identifying who or what is making the request. The default is
NULL that causes the framework to call a global function
AfxGetAppName that returns the application name. Following is a
dwContext associated with the identifier of the operation that identifies the status information returned by
CInternetSession::OnStatusCallback; the default is 1. If
INTERNET_FLAG_ASYNC, then all objects created by
CInternetSession will have asynchronous behavior, but a status callback routine must be registered (explanation follows later.). Next,
dwAccessType describes the type of access to be used and only one of the following can be used,
INTERNET_OPEN_TYPE_PRECONFIG - Gets the preconfigured information from the registry that happens to be the default.
INTERNET_OPEN_TYPE_DIRECT - Direct to the internet.
INTERNET_OPEN_TYPE_PROXY - Use a proxy to access the Internet.
pstrProxyName is a pointer to a string referring the name of the proxy to use if
dwAccessType is set to
INTERNET_OPEN_TYPE_PROXY. The default is
pstrProxyBypass is a pointer to a string referring to a list of optional server addresses if
NULL. The list is fetched from the registry. Just as
INTERNET_OPEN_TYPE_PROXY must be specified for
dwFlags is to set the options of caching and asynchronous behavior. The default is 0. This can contain any of the following flags,
INTERNET_FLAG_DONT_CACHE - Do not cache anything.
INTERNET_FLAG_ASYNC - Enables asynchronous operations as long as a status callback is registered using the function
INTERNET_FLAG_OFFLINE - Use the Internet cache folder, if the requested data isn't found then corresponding error is returned.
Then we can set the various options.
session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 1,000 * 20);
Look in the wininet.h file to see all the options we can use. Many of them there.
Then we can set the status call back routine if we wish to use callbacks. Either set it to
TRUE (enable) or
Again, look in your wininet.h file for the various callbacks you can react to in your callback routine.
If we use status call backs, make sure they are thread safe protected in the implementation of our callback routine with,
Second, we need to connect to the server using
CinternetSession::GetHttpConnection. We will need a pointer to a string as the address of the server we are making the connection to, an unsigned short
int of the port number we will use for this connection, a pointer to a string containing the username and a pointer to a string containing a password if we are retrieving any data that is password protected. The last two parameters are
NULL as the default.
CHttpConnection* pServer = NULL;
pServer = session.GetHttpConnection(lpstrServer,
Third, we need to open an HTTP request using
CHttpFile* pFile = NULL;
pFile = pServer->OpenRequest(pstrVerb,
OpenRequest needs a pointer to a string containing the verb for this request that can be either
GET will be used. It also needs a pointer to a string containing the file, executable module, even a search specifier that will be associated with the verb that was just specified. Also required is a pointer to a string containing the address from which this request obtained from, if
NULL no HTTP header is specified; a
DWORD for the context identifier of the
OpenRequest, 1 is the default and this
DWORD is associated with the specific
ChttpConnection object created by the
CInternetSession object; a pointer to a null terminated string containing the content type the client accepts. Specifying
NULL tells the server the client only accepts documents of type
AcceptTypes is the equivalent of the CGI variable
OpenRequest needs a pointer string containing the HTTP version that is used, if
HTTP/1.0 will be used. Last, a
DWORD specifying the HTTP request flags to be set. Any of the following can be used.
INTERNET_FLAG_RELOAD - Forces a download from the server and not from the cache.
INTERNET_FLAG_DONT_CACHE - Do not add any data returned from the server to the cache.
INTERNET_FLAG_MAKE_PERSISTENT - Add the returned data from the server as a persistent object in the cache.
INTERNET_FLAG_SECURE - Use secure transactions via SSL/PCT.
INTERNET_FLAG_NO_AUTO_REDIRECT - Do not allow redirections to be automatically handled.
Now we can just add the various request headers and send the request, hoping for the best if the connection didn't return an error. To add the various headers we can use,
BOOL AddRequestHeaders( CString& str,
DWORD dwFlags = HTTP_ADDREQ_FLAG_ADD_IF_NEW );
We can use a null terminated string containing the headers we wish to add.
CString szHeaders = "Accept: audio/x-aiff, audio/basic, audio/midi,
audio/mpeg, audio/wav, image/jpeg, image/gif, image/jpg, image/png,
image/mng, image/bmp, text/plain, text/html, text/htm\r\n";
dwFlags can contain any of the following.
HTTP_ADDREQ_FLAG_COALESCE - Merges all headers of the same name separated by a comma.
HTTP_ADDREQ_FLAG_REPLACE - Remove and add to replace the current header.
HTTP_ADDREQ_FLAG_ADD_IF_NEW - Only adds the header if one doesn't exist.
HTTP_ADDREQ_FLAG_ADD - Used this with REPLACE and it adds the header if it doesn't exist.
Then just send the request,
Then we can query the status of our request that will tell us if the file is there, missing, redirected, etc.
Then act accordingly depending on the status code.
if (dwRet == HTTP_STATUS_DENIED)
if (dwRet == HTTP_STATUS_MOVED ||
dwRet == HTTP_STATUS_REDIRECT ||
dwRet == HTTP_STATUS_REDIRECT_METHOD)
If everything went well,
QueryInfoStatusCode will return
HTTP_STATUS_OK. Then we can allocate a buffer, create our file to write data in the buffer to, read the file until the end of file and do any clean up we need to do once we're done. Not forgetting to handle any exceptions we may come across and close our
if(dwRet == HTTP_STATUS_OK)
int len = pFile->GetLength();
while ((numread = pFile->Read(buf,sizeof(buf)-1)) > 0)
buf[numread] = '\0';
strFile += buf;
To use the class that I provide in the demo, the function to call is
DWORD CMyInternetSession::GetWebFile(LPCTSTR pstrAgent,
LPCTSTR lpstrServer, int nPort, CString strFile);
GetWebFile will return a
DWORD associated with the returned HTTP error code.
pstrAgent is a string containing the client name,
lpstrServer is a string containing the server address,
nPort is an
int of the port number to use,
strFile is a
CString string of the filename to fetch from the server.
Also the following function,
void CMyInternetSession::ShowStatus(LPCTSTR strStatus)
CGetWebFileDlg * pWebFileDlg = (CGetWebFileDlg*) AfxGetMainWnd();
should handle any displaying of status information in any fashion at the programmer's wish.
TIP: With the platform SDK, a debug version of wininet.dll can be had that comes in very handy in debugging.
Then we're done! Hope this has been of some insight to anyone looking for an example with explanation on the subject. Just include MyInternetSession.cpp and MyInternetSession.h into your project and edit it to your liking. Take a close look at the function
ShowStatus. In addition, I used a peek and pump that I borrowed from Chris Maunder within the file reading routine, and use a stock MFC progress control. I would change these to your liking. Many possibilities can be implemented, one could change it to a function within a thread and so on.
If any bugs, ideas or additions, just leave the author an e-mail. Kudos! Constructive criticism is welcomed.
- Implementing a pointer to a status or edit control specified by the programmer.
- Better handling of error control.
- Implementing the
HTTP_STATUS_DENIED status return code in a more productive way.
- Various information from - MSDN