Writing a HTTP client with Microsoft WinHTTP 5.0
Posted: (EET/GMT+2)
Writing a HTTP client with Microsoft WinHTTP 5.0
Dec 6, 2001
Borland Delphi 6 or later
Microsoft Windows HTTP Services 5.0 (WinHTTP) SDK installed
Microsoft's Windows HTTP Services 5.0 (WinHTTP) is the latest version of the company's HTTP client application programming API. This API allows you to easily create advanced HTTP client applications. Unlike the WinInet API, WinHTTP is designed for server applications that don't require a user interface.
Before you can start to use the WinHTTP API with for example Borland Delphi 6, you need to download the WinHTTP SDK from the Microsoft MSDN site. Once on the site, search for "winhttp" and you will find a page that contains a download link. Installing the SDK is simple, but remember that you have to have a NT based operating system, namely Windows NT 4.0, Windows 2000 or Windows XP.

Once installed, you should find documentation, sample applications and C language header files under the installation directory (or should that be "folder"?). If you are using Delphi for your development, you of course need a way to use the APIs from your environment. This requires that the header file WINHTTP.H to be translated to Object Pascal.
The following is a simple translation of the most common structures. You can also download the file WINHTTP.PAS directly by clicking the link at the bottom of this article.
unit WinHTTP;
{$ALIGN 4 - set record field align to 4 bytes }
interface
Uses Windows;
Type
HINTERNET = Pointer;
PHINTERNET = ^HInternet;
INTERNET_PORT = Word;
PINTERNET_PORT = ^INTERNET_PORT;
PURL_COMPONENTS = ^TURL_COMPONENTS;
TURL_COMPONENTS = Record
dwStructSize : Integer; { size of this structure. Used in version check }
lpszScheme : PWideChar; { pointer to scheme name }
dwSchemeLength : Integer; { length of scheme name }
nScheme : Integer; { enumerated scheme type (if known) }
lpszHostName : PWideChar; { pointer to host name }
dwHostNameLength : Integer; { length of host name }
nPort : INTERNET_PORT; { converted port number }
lpszUserName : PWideChar; { pointer to user name }
dwUserNameLength : Integer; { length of user name }
lpszPassword : PWideChar; { pointer to password }
dwPasswordLength : Integer; { length of password }
lpszUrlPath : PWideChar; { pointer to URL-path }
dwUrlPathLength : Integer; { length of URL-path }
lpszExtraInfo : PWideChar; { pointer to extra information (e.g. ?foo or #foo) }
dwExtraInfoLength : Integer; { length of extra information }
End;
Const
WinHTTP5DLL = 'winhttp5.dll';
INTERNET_DEFAULT_PORT = 0;
INTERNET_DEFAULT_HTTP_PORT = 80;
INTERNET_DEFAULT_HTTPS_PORT = 443;
INTERNET_SCHEME_HTTP = 1;
INTERNET_SCHEME_HTTPS = 2;
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0;
WINHTTP_ACCESS_TYPE_NO_PROXY = 1;
WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3;
WINHTTP_NO_REFERER = nil;
WINHTTP_DEFAULT_ACCEPT_TYPES = nil;
WINHTTP_NO_ADDITIONAL_HEADERS = nil;
WINHTTP_NO_REQUEST_DATA = nil;
Function WinHttpOpen(pwszUserAgent : PWideChar; dwAccessType : Integer;
pwszProxyName : PWideChar; pwszProxyBypass : PWideChar;
dwFlags : Integer) : HINTERNET;
StdCall; External WinHTTP5DLL;
Function WinHttpCloseHandle(Handle : HInternet) : Bool;
StdCall; External WinHTTP5DLL;
Function WinHttpCheckPlatform : Bool;
StdCall; External WinHTTP5DLL;
Function WinHttpConnect(hSession : HINTERNET; pswzServerName : PWideChar;
nServerPort : Word; dwReserved : Integer) : HINTERNET;
StdCall; External WinHTTP5DLL;
Function WinHttpOpenRequest(hConnect : HINTERNET; pwszVerb : PWideChar;
pwszObjectName : PWideChar; pwszVersion : PWideChar;
pwszReferrer : PWideChar; ppwszAcceptTypes : Pointer;
dwFlags : Integer) : HINTERNET;
StdCall; External WinHTTP5DLL;
Function WinHttpSendRequest(hRequest : HINTERNET;
pwszHeaders : PwideChar; dwHeadersLength : Integer;
lpOptional : Pointer; dwOptionalLength : Integer;
dwTotalLength : Integer; dwContext : PInteger) : Bool;
StdCall; External WinHTTP5DLL;
Function WinHttpReceiveResponse(hRequest : HINTERNET;
lpReserved : Pointer) : Bool;
StdCall; External WinHTTP5DLL;
Function WinHttpQueryDataAvailable(hRequest : HINTERNET;
Var lpdwNumberOfBytesAvailable : Integer) : Bool;
StdCall; External WinHTTP5DLL;
Function WinHttpReadData(hRequest : HINTERNET; lpBuffer : Pointer;
dwNumberOfBytesToRead : Integer;
Var lpdwNumberOfBytesRead : Integer) : Bool;
StdCall; External WinHTTP5DLL;
Function WinHttpQueryHeaders(hRequest : HINTERNET; dwInfoLevel : Integer;
pwszName : PWideChar; lpBuffer : Pointer;
Var lpdwBufferLength : Integer; lpdwIndex : PInteger) : Bool;
StdCall; External WinHTTP5DLL;
Function WinHttpCrackUrl(pwszUrl : PWideChar; dwUrlLength : Integer;
dwFlags : Integer; Var lpUrlComponents : TURL_COMPONENTS) : Bool;
StdCall; External WinHTTP5DLL;
implementation
end.
Note how the actual API functions are implemented in WINHTTP5.DLL.
Developing a simple client
When developing web applications, you sometimes need a way to simply see the HTTP headers and body without any formatting. If you are using a web browser, you can of course choose to view the source, but that only displays the HTML code, not the HTTP headers.
The following is part of the code of an application dubbed HTTPGet. This application is a simple one; it simply takes in a URL as a command line parameter, and outputs the given URL along with headers to the console.
Program HTTPGet;
{$APPTYPE CONSOLE}
Uses
SysUtils,
WinHTTP in 'WinHTTP.pas';
Var
Session : HINTERNET;
NoBody : Boolean;
Quiet : Boolean;
Procedure CheckParams;
Begin
If ((ParamCount = 0) Or FindCmdLineSwitch('?')) Then Begin
WriteLn('HTTPGet v1.0 Copyright (C) WhirlWater 2001.');
WriteLn('This program simply "gets" the resource pointed to by the given HTTP URL.');
WriteLn('The purpose of this application is to quickly retrieve HTTP headers and body');
WriteLn('for example debugging purposes. It can also used to test whether an URL could');
WriteLn('pose a security risk, such as a browser-enabled virus and/or script code.');
WriteLn;
WriteLn('This program requires Microsoft Windows HTTP Services (WinHTTP) 5.0 to operate.');
WriteLn;
WriteLn('Usage: HTTPGET [URL [-n] [-q]]');
WriteLn;
WriteLn('Parameters: URL = the URL to fetch, must include "http://"');
WriteLn(' -n = don''t display the resource body, just the headers');
WriteLn(' -q = quiet operation, just display data and error messages');
Halt(1);
End;
NoBody := FindCmdLineSwitch('N');
Quiet := FindCmdLineSwitch('Q');
End;
Procedure FetchURL;
Var
URL : String;
Host,Path : String;
Failed : Boolean;
Begin
Failed := False;
Try
URL := ParamStr(1);
OpenWinHTTPSession;
CrackURL(URL,Host,Path);
DoURLFetch(Host,Path);
WinHttpCloseHandle(Session);
If (Not Quiet) Then WriteLn('---------------'#13#10'Done.');
Except
On E : Exception do Begin
Failed := True;
WriteLn('HTTPGen: '+E.Message);
End;
End;
If Failed Then Halt(2);
End;
Begin
CheckParams;
FetchURL;
End.
Here, the code first reads the given command line parameters and then continues to initialize WinHTTP and then fetch the URL. Describing the WinHTTP API itself is beyond the scope of this article, but it's nice to know that all WinHTTP APIs begin with "WinHttp". This makes it easy to distinguish between custom function and WinHTTP API calls.
Before WinHTTP can be utilized, it needs to be initialized. This can be done with the WinHttpOpen function. The following code illustrates:
Procedure OpenWinHTTPSession;
Begin
Session := WinHttpOpen('WhirlWater HTTPGet 1.0',
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,nil,nil,0);
If (Session = nil) Then RaiseLastOSError;
End;
Note that if you are using an older version of Delphi such as Delphi 5, you can replace the RaiseLastOSError procedure call with RaiseLastWin32Error procedure. These procedures can be considered equal.
Cracking URLs
WinHTTP requires that URLs are "cracked" or divided to their parts before the given URL can be fetched. For example, when you want to connect a HTTP server with a call to WinHttpConnect, you need to have the host name, not the full URL.
Because writing your custom parsing procedures can be tedious, WinHTTP provides a convenient function named WinHttpCrackUrl. The following code snippet shows how to use this function in real code:
Procedure CrackURL(URL : String; Var Host,Path : String);
Var
Comp : TURL_COMPONENTS;
URLW : WideString;
Begin
FillChar(Comp,SizeOf(Comp),0);
With Comp do Begin
dwStructSize := SizeOf(Comp);
dwHostNameLength := -1;
dwUrlPathLength := -1;
dwExtraInfoLength := -1;
End;
URLW := URL;
If (Not WinHttpCrackUrl(PWideChar(URLW),0,0,Comp)) Then ShowWinHTTPError;
Host := Copy(Comp.lpszHostName,1,Comp.dwHostNameLength);
If (Comp.dwUrlPathLength = 0) Then Path := '/'
Else Path := WideString(Comp.lpszUrlPath);
End;
Note that the strings WinHTTP manipulates are mostly Unicode. Fortunately Delphi makes it easy to convert between Unicode and ANSI strings - you simply need to do an assignment.
Fetching URLs
Once you have an URL cracked, it's time to fetch the given resource and output it to the standard output (stdout). The following code does just this:
Procedure DoURLFetch(Host,Path : String);
Var
Connection : HINTERNET;
Request : HINTERNET;
Size : Integer;
Read,Status : Integer;
Buffer : PChar;
Headers : PWideChar;
Data,Header : String;
Begin
Connection := WinHttpConnect(Session,PWideChar(WideString(Host)),
INTERNET_DEFAULT_HTTP_PORT,0);
If (Connection = nil) Then ShowWinHTTPError;
Request := WinHttpOpenRequest(Connection,'GET',
PWideChar(WideString(Path)),nil,nil,
WINHTTP_DEFAULT_ACCEPT_TYPES,0);
If (Request = nil) Then ShowWinHTTPError;
If (Not Quiet) Then WriteLn('Sending HTTP request...');
If (Not WinHttpSendRequest(Request,WINHTTP_NO_ADDITIONAL_HEADERS,
0,WINHTTP_NO_REQUEST_DATA,0,0,nil)) Then ShowWinHTTPError;
If (Not Quiet) Then WriteLn('HTTP request sent...');
If (Not WinHttpReceiveResponse(Request,nil)) Then ShowWinHTTPError;
If (Not Quiet) Then WriteLn('HTTP response received...');
{ headers }
WinHttpQueryHeaders(Request,WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,WINHTTP_NO_OUTPUT_BUFFER,
Size,WINHTTP_NO_HEADER_INDEX);
GetMem(Headers,Size*SizeOf(WideChar)+1);
Try
If (Not WinHttpQueryHeaders(Request,WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,Headers,Size,
WINHTTP_NO_HEADER_INDEX)) Then ShowWinHTTPError
Else Header := WideCharToString(Headers);
Finally
FreeMem(Headers);
End;
If (Not Quiet) Then WriteLn('HTTP response headers reads...');
{ body }
If (Not WinHttpQueryDataAvailable(Request,Size)) Then ShowWinHTTPError
Else Begin
Data := '';
While (Size > 0) do Begin
GetMem(Buffer,Size+1);
Try
If (Not WinHttpReadData(Request,Buffer,Size,Read)) Then ShowWinHTTPError
Else Begin
Buffer[Read] := #0; { add string terminator }
Data := Data+Buffer;
End;
Finally
FreeMem(Buffer);
End;
If (Not WinHttpQueryDataAvailable(Request,Size)) Then ShowWinHTTPError;
End;
{ all data fetched, now get the HTTP status code }
Size := SizeOf(Status);
If (Not WinHttpQueryHeaders(Request,WINHTTP_QUERY_STATUS_CODE Or
WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_NO_OUTPUT_BUFFER,@Status,Size,
nil)) Then ShowWinHTTPError;
End;
If (Not Quiet) Then WriteLn('All HTTP data read...'#13#10'---------------');
WinHttpCloseHandle(Request);
WinHttpCloseHandle(Connection);
{ finish with the results }
Write(Header);
If (Not NoBody) Then WriteLn(Data);
End;
Here, the most important functions are WinHttpSendRequest, WinHttpReceiveResponse, WinHttpQueryHeaders and WinHttpReadData. Of course, you will need other functions as well, but focusing on these in the above code makes it easier to understand what's going on.
Once a request has been sent out with WinHttpSendRequest, the WinHttpReceiveResponse function can be called. This function blocks until the server responds or a timeout (by default, 60 seconds) occurs. If a response could be successfully read, then the example application enters a loop that fetches all the data from the response and writes it to the console. Also note how the WinHttpQueryHeaders is used to retrieve and display the HTTP headers.
The following picture illustrates a sample run with the -n switch:

Easy, aint it?
Download the example code
Download writingahttpclientwithwinhttp5.zip (33 kB) which contains the sample application HTTPGet. Please note that the sample application will require Delphi 6 or later as well as Microsoft WinHTTP SDK 5.0 installed. Future Microsoft .NET operating systems might contain the necessary DLLs already installed.
* * *
Need help developing cool Internet applications with Delphi? Contact us!