Download a file from the Internet reliably

Submitted on: 1/3/2015 5:55:00 AM
By: Dan Tohatan (from psc cd)  
Level: Intermediate
User Rating: By 10 Users
Compatibility: C, C++ (general), Microsoft Visual C++, Borland C++
Views: 1783
     In this article you will find out why you just can't call a few WinInet functions and expect them to download a file for you. You will learn the proper way to download a file from the Internet, and display its progress and download speed!

This article has accompanying files
				First, create a function to handle downloading of a file from the web. My example is like this:

UINT InternetGetFile (HINTERNET IN hOpen, CHAR *szUrl, CHAR *szFileName, HWND hwndProgress, int idStatusText, int idProgressBar);

It's UINT because it will return a value if an error occurs. Otherwise, it will return 0. In order to use this function, all you have to do is provide a valid HINTERNET handle obtained using a standard InternetOpen() call. If you want, you can provide a handle to a progress window (and an ID for the STATIC control for status, and the PROGRESS control for the progress bar), where the function will display its status. Everything else is self-explanatory. I begin the body of the function declaring some variables.
DWORD dwSize;

This variable will be used to store how much data is read with every call to InternetReadFile.
CHAR szHead[] = "Accept: */*\r\n\r\n";

In this variable, the much-needed HTTP header is stored. If you do not pass this header onto the InternetOpenUrl call, it will only allow you to open text files!
VOID* szTemp[16384];

This variable is a buffer in which 16 KB of data from the Internet file will be stored.

This is a HINTERNET handle containing the request result (from InternetOpenUrl).
FILE * pFile;

A standard C file handle(must include stdio.h). If you wish, you can use file APIs from Win32.
if (!(hConnect = InternetOpenUrlA (hOpen, szUrl, szHead, lstrlenA (szHead), INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD, 0)))

With this call, you open a handle to the Internet file using a URL. The flags indicate that the file will always be read from the Internet rather than the cache. If it fails, the function returns an error. You can give INTERNET_ERROR_OPENURL any value you like. You have to define all these errors in order for this function to work. You can also replace it with a number.
if(!(pFile = fopen(szFileName, "wb" )))

With this call, an attempt is made to open a file with the given file name. If it fails, another custom-defined error is returned.
DWORD dwByteToRead = 0;
DWORD dwSizeOfRq = 4;
DWORD dwBytes = 0;

These three variables will store the size of the file, the size of the HttpQueryInfo content, and the number of bytes read in total, respectively.
if (!HttpQueryInfo(hConnect, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, (LPVOID)&dwByteToRead, &dwSizeOfRq, NULL))
dwByteToRead = 0;

With this call, an attempt is made to get the file's size. If the attempt fails, the dwByteToRead variable is set to 0, and no percentage or total size is displayed when the file is downloaded.
DWORD start;
DWORD end;
DWORD time;
time = 10;
start = timeGetTime();

For this bit, you will need to include mmsystem.h and link with the library winmm.lib, to allow these timing features. The timing features will tell the user how fast the download is. Using this example, only the Internet download speed will be stated. However, you can expand this functionality to estimate the time remaining as well.
if (!InternetReadFile(hConnect, szTemp, 16384, &dwSize))
fclose (pFile);

With this part, a loop begins in which the file is downloaded in 16 KB chunks. If the download request fails, the file is closed, and an error is returned.
if (!dwSize)
fwrite(szTemp, sizeof(char), dwSize, pFile);

If dwSize is 0, it means EOF has been encountered, so the loop exits. Otherwise, the contents of the data read by InternetReadFile are written to the local file.
if(dwByteToRead && hwndProgress)
SendDlgItemMessageA(hwndProgress, idProgressBar, WM_USER+2, (dwBytes*100)/dwByteToRead, 0);

With this code, dwBytes increases by the amount of data read from the file, and if the file's length is valid, and a progress window handle was specified, the progress bar is updated to indicate the download's progress.
FLOAT fSpeed = 0;
fSpeed = (float)dwBytes;
fSpeed /= ((float)time)/1000.0f;
fSpeed /= 1024.0f;

With this bit of code, the speed of the download is calculated based on the time it took, and the amount of data read.
char s[260];
sprintf(s, "%d KB / %d KB @ %1.1f KB/s", dwBytes/1024, dwByteToRead/1024, fSpeed);
SetDlgItemTextA(hwndProgress, idStatusText, s);

With this code, the status text of the progress window is set to indicate the download size and the speed of the download.
end = timeGetTime();
time = end - start;
if(time == 0)
time = 10;

The time is updated.

} // do
while (TRUE);

The loop ends.
fflush (pFile);
fclose (pFile);
return 0;

And finally, our function ends, closing the file and flushing the buffer to the hard drive. It returns 0, meaning success.
And that's it! With this bit of code and knowledge you can finally download files from the Internet efficiently and reliably. The full code example will follow.

winzip iconDownload article

Note: Due to the size or complexity of this submission, the author has submitted it as a .zip file to shorten your download time. Afterdownloading it, you will need a program like Winzip to decompress it.Virus note:All files are scanned once-a-day by Planet Source Code for viruses, but new viruses come out every day, so no prevention program can catch 100% of them. For your own safety, please:
  1. Re-scan downloaded files using your personal virus checker before using it.
  2. NEVER, EVER run compiled files (.exe's, .ocx's, .dll's etc.)--only run source code.

If you don't have a virus scanner, you can get one at many places on the net

Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)

Other User Comments

 There are no comments on this submission.

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)

To post feedback, first please login.