Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2 and some Figure screen snapshots have been taken on Windows 2000 server. The Internet Information Services version is IIS 4.x/5.x/6.x on Windows 2000 Server SP 4 and Windows XP Pro SP 2. The Internet Explorer is 6.x. Topics and sub topics for this tutorial are listed below. A complete information about IIS installation, configuration and testing a Web site is dumped HERE and how to setup FTP server also included HERE. Both Web and FTP servers were done on Windows 2000 Server SP4. Don’t forget to read Tenouk’s small disclaimer.
The Story - The First Step: Getting the Order
Junior sales trainees are constantly admonished to "get the order." That's certainly necessary in any form of commerce, including the Internet. When the hungry customer hyperlinks to your site (by clicking on a picture of a pizza, of course), he or she simply downloads an HTML file that looks like this:
<TITLE>Inside Visual C++ HTML Form 1</TITLE>
<CENTER><B><FONT color="#0000FF" face="Verdana" size="4">Welcome to CyberPizza</FONT></B></CENTER>
<P><FONT color="#FF0000" face="Tahoma">Enter your order.</FONT> </P>
<FORM action="scripts/myex34a.dll?ProcessPizzaForm" method="POST">
<P>Your Name: <INPUT type="text" name="name" value> </P>
<P>Your Address: <INPUT type="text" name="address" value> </P>
<P>Number of Pies: <INPUT type="text" name="quantity" value="1"> </P>
<P>Pizza Size: </P>
<LI><INPUT type="radio" name="size" value="8">8-inch </LI>
<LI><INPUT type="radio" name="size" value="10">10-inch </LI>
<LI><INPUT type="radio" name="size" value="12" checked>12-inch </LI>
<LI><INPUT type="radio" name="size" value="14">14-inch </LI>
<P><INPUT type="checkbox" name="top1" value="Pepperoni" checked> Pepperoni
<INPUT type="checkbox" name="top2" value="Onions"> Onions
<INPUT type="checkbox" name="top3" value="Mushrooms"> Mushrooms
<INPUT type="checkbox" name="top4" value="Sausage"> Sausage </P>
<P><EM>(you can select multiple toppings)</EM> </P>
<P><INPUT type="submit" value="Submit Order Now"><INPUT type="reset"> </P>
Figure 46 shows how the order form appears in the browser.
Figure 46: The CyberPizza order form.
So far, no ISAPI DLL is involved. When the customer clicks the Submit Order Now button, the action begins. Here's an example what the server sees:
POST scripts/myex34a.dll?ProcessPizzaForm HTTP/1.0
Looks like Walter Sullivan has ordered two 12-inch pepperoni and mushroom pizzas. The browser inserts a + sign in place of a space, the %2C is a comma, and the & is the parameter separator. Now let's look at the parse map entries in myex34a.cpp:
ITS_PSTR ITS_PSTR ITS_I4 ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("name address quantity size top1=~ top2=~ top3=~ top4=~")
When you write your parse map statements, you must understand the browser's rules for sending parameter values from a form. In the MYEX34A pizza form, the browser always sends parameters for text fields, even if the user enters no data. If the user left the Name field blank, for example, the browser would send name=&. For check box fields, however, it's a different story. The browser sends the check box parameter value only if the user checks the box. The parameters associated with check boxes are thus defined as optional parameters. If your parse macro for parameters looked like this:
ON_PARSE_COMMAND_PARAMS("name address quantity size top1 top2 top3 top4")
there would be trouble if the customer didn't check all the toppings. The HTTP request would simply fail, and the customer would have to search for another pizza site. The =~ symbols in the myex34a.cpp code designate the last four parameters as optional, with default values ~. If the Toppings option is checked, the form transmits the value; otherwise, it transmits a ~ character, which the DLL can test for. Optional parameters must be listed last. The DLL's ProcessPizzaForm() function reads the parameter values and produces an HTML confirmation form, which it sends to the customer. Here is part of the function's code:
*pCtxt << "<form action=\"myex34a.dll?ConfirmOrder\" method=POST>";
*pCtxt << "<p><input type=\"hidden\" name=\"name\" value=\"";
*pCtxt << pstrName << "\">"; // xref to original order
*pCtxt << "<p><input type=\"submit\" value=\"Confirm and charge my credit card\">";
*pCtxt << "</form>";
// Store this order in a disk file or database, referenced by name
*pCtxt << "You forgot to enter name or address. Back up and try again. ";
The resulting browser screen is shown in Figure 47.
Figure 47: The pizza confirmation browser screen.
As you can see, we took a shortcut computing the price. To accept, the customer clicks the submit button named Confirm And Charge My Credit Card.
The Second Step: Processing the Confirmation
When the user clicks the Confirm And Charge My Credit Card button, the browser sends a second POST request to the server, specifying that the CMyex34aExtension::ConfirmOrder function be called. But now you have to solve a big problem. Each HTTP connection (request/response) is independent of all others. How are you going to link the confirmation request with the original order? Although there are different ways to do this, the most common approach is to send some text back with the confirmation in a hidden input tag. When the confirmation parameter values come back, the server uses the hidden text to match the confirmation to the original order, which it has stored somewhere on its hard disk. In the MYEX34A example, the customer's name is used in the hidden field, although it might be safer to use some combination of the name, date, and time. Here's the HTML code that CMyex34aExtension::ProcessPizzaForm sends to the customer as part of the confirmation form:
<input type="hidden" name="name" value="Walter Sullivan">
Here's the code for the CMyex34aExtension::ConfirmOrder function:
void CMyex34aExtension::ConfirmOrder(CHttpServerContext* pCtxt, LPCTSTR pstrName)
*pCtxt << "<p>Our courteous delivery person will arrive within 30 minutes. ";
*pCtxt << "<p>Thank you, " << pstrName << ", for using CyberPizza. ";
// Now retrieve the order from disk by name, and then make the pizza.
// Be prepared to delete the order after a while if the customer doesn't confirm.
m_cs.Lock(); // gotta be threadsafe
long int nTotal = ++m_nTotalPizzaOrders;
*pCtxt << "<p>Total pizza orders = " << nTotal;
The customer's name comes back in the pstrName parameter, and that's what you use to retrieve the original order from disk. The function also keeps track of the total number of orders, using a critical section (m_cs) to ensure thread synchronization.
Building and Testing myex34a.dll
Building the project adds a DLL to the Debug subdirectory. You must copy this DLL to a directory that the server can find and copy PizzaForm.html also. You can use the scripts and wwwroot subdirectory that already under \Inetpub, or you can set up new virtual directories.
If you make changes to the MYEX34A DLL in the Visual C++ project, be sure to use Internet Service Manager to turn off the WWW service (because the old DLL stays loaded), copy the new DLL to the scripts directory, and then turn the WWW service on again. The revised DLL will be loaded as soon as the first client requests it. If everything has been installed correctly, you should be able to load PizzaForm.html from the browser and then order some pizza. Enjoy!
Debugging the MYEX34A DLL
The fact that IIS is a Windows NT service complicates debugging ISAPI DLLs. Services normally run as part of the operating system, controlled by the service manager database. They have their own window station, and they run on their own invisible desktop. This involves some of the murkier parts of Windows NT, and not much published information is available. However, you can use these steps to debug your MYEX34A DLL (or any ISAPI DLL):
Figure 48: Stopping the IIS Admin and other dependent (World Wide Web publishing) services.
C:\WINDOWS\system32\inetsrv\inetinfo.exe or C:\WINNT\system32\inetsrv\inetinfo.exe depends on your Windows OS version.
Figure 49: Entering the program that will be invoked during the debug process.
Figure 50: Changing the User Right Assignment setting through Local Security Settings.
Figure 51: Adding a user to the Act as part of the operating system policy.
Figure 52: Generate Security Audits policy.
ISAPI Database Access
Your ISAPI server extension could use ODBC to access an SQL database. Before you write pages of ODBC code, however, check out the Internet Database Connector (IDC) described in the IIS documentation. The Internet Database Connector is a ready-to-run DLL, Httpodbc.dll, that collects SQL query parameters and formats the output. You control the process by writing an IDC file that describes the data source and an HTX file that is a template for the resulting HTML file. No C++ programming is necessary. The Internet Database Connector is for queries only. If you want to update a database, you must write your own ISAPI server extension with ODBC calls. Make sure your ODBC driver is multithreaded, as is the latest SQL server driver.
Using HTTP Cookies to Link Transactions
Now that you've wolfed down the pizza, it's time for some dessert. However, the cookies that we'll be digesting in this section are not made with chocolate chips. Cookies are used to store information on our customers' hard disks. In the MYEX34A example, the server stores the customer name in a hidden field of the confirmation form. That works fine for linking the confirmation to the order, but it doesn't help you track how many pizzas Walter ordered this year. If you notice that Walter consistently orders pepperoni pizzas, you might want to send him some e-mail when you have a surplus of pepperoni.
How Cookies Work
With cookies, you assign Walter a customer ID number with his first order and make him keep track of that number on his computer. The server assigns the number by sending a response header such as this one:
Set-Cookie: customer_id=12345; path=/; expires=Monday, 02-Sep-05 00:00:00 GMT
The string customer_id is the arbitrary cookie name you have assigned, the / value for path means that the browser sends the cookie value for any request to your site (named CyberPizza.com), and the expiration date is necessary for the browser to store the cookie value. When the browser sees the Set-Cookie response header, it creates (or replaces) an entry in its cookies.txt file as follows:
Every user profile will have cookies.txt. The following Figure shows the example.
Figure 53 Cookies.txt for user profiles in Windows XP Pro.
Figure 54: Cookies.txt’s content example of the user profile for Firefox browser.
Thereafter, when the browser requests anything from CyberPizza.com, the browser sends a request header like this:
How an ISAPI Server Extension Processes Cookies
Your ISAPI server extension function makes a call like this one to store the cookie at the browser:
AddHeader(pCtxt, "Set-Cookie: session_id=12345; path=/;"" expires=Monday, " 02-Sep-05 00:00:00 GMT\r\n");
To retrieve the cookie, another function uses code like this:
DWORD dwLength = 200;
pCtxt->GetServerVariable("HTTP_COOKIE", strCookies, &dwLength);
The strCookies variable should now contain the text customer_id=12345.
Problems with Cookies
Up to now, your IIS has been set to allow anonymous logons, which means that anyone in the world can access your server without supplying a user name or password. All users are logged on as IUSR_MYMACHINENAME and can access any files for which that user name has permissions. As stated in Module 32, you should be using NTFS on your server for maximum security.
The simplest way to limit server access is to enable basic authentication. Then, if a client makes an anonymous request, the server sends back the response:
HTTP/1.0 401 Unauthorized
together with a response header like this:
WWW-Authenticate: Basic realm="xxxx"
The client prompts the user for a user name and password, and then it resends the request with a request header something like this:
Authorization: Basic 2rc234ldfd8kdr
The string that follows Basic is a pseudoencrypted version of the user name and password, which the server decodes and uses to impersonate the client. The trouble with basic authentication is that intruders can pick up the user name and password and use it to gain access to your server. IIS and most browsers support basic authentication, but it's not very effective.
Windows NT Challenge/Response Authentication
Windows NT challenge/response authentication is often used for intranets running on Microsoft networks, but you can use it on the Internet as well. IIS supports it, but not all browsers do. If the server has challenge/response activated, a client making an ordinary request gets this response header:
Authorization: NTLM T1RMTVNTUAABAAAAA5IAA ...
The string after NTLM is the well-encoded user name, the password is never sent over the network. The server issues a challenge, with a response header like this:
WWW-Authenticate: NTLM RPTUFJTgAAAAAA ...
The client, which knows the password, does some math on the challenge code and the password and then sends back a response in a request header like this:
Authorization: NTLM AgACAAgAAAAAAAAAA ...
The server, which has looked up the client's password from the user name, runs the same math on the password and challenge code. It then compares the client's response code against its own result. If the client's and the server's results match, the server honors the client's request by impersonating the client's user name and sending the requested data.
When the client resends the request, the challenge/response dialog is performed over a single-socket connection with keep-alive capability as specified in the Connection request header.
WinInet fully supports Windows NT challenge/response authentication. Thus, Internet Explorer 4.0/above and the MYEX34A WinInet clients support it. If the client computer is logged on to a Windows NT domain, the user name and password are passed through. If the client is on the Internet, WinInet prompts for the user name and password. If you're writing WinInet code, you must use the INTERNET_FLAG_KEEP_CONNECTION flag in all CHttpConnection::OpenRequest and CInternetSession::OpenURL calls as EX34A illustrates.
The Secure Sockets Layer
Windows NT challenge/response authentication controls only who logs on to a server. Anyone snooping on the Net can read the contents of the TCP/IP segments. The secure sockets layer (SSL) (the open source version – OpenSSL) goes one step further and encodes the actual requests and responses (with a performance hit, of course). Both IIS and WinInet support SSL. The secure sockets layer is described in the IIS documentation. The newest one is Transport Layer Security (TLS).
Continue on next Module...
Further reading and digging:
Win32 process, thread and synchronization story can be found starting from Module R.
DCOM at MSDN.
COM+ at MSDN.
COM at MSDN.