C++Builder comes with a variety of useful Internet components used for sending and receiving data over TCP/IP networks. They are the building blocks for n-tier application frameworks such as Borland’s MIDAS, and can also be used to implement any other application requiring communication over TCP/IP (such as peer-to-peer applications). The TServerSocket component can be used to listen on a TCP/IP port, and respond to messages received on that port.
It’s easy to use TServerSocket to create a Web server. Why write a Web server when there are many free Web servers out there for the platform of your choice? If the only function of your Web server were to serve up static Web pages, then it wouldn’t make much sense. But having a Web server embedded in an application turns out to be a powerful thing. You can instantly add a Web interface to your application without having to write a middle tier to handle communication between the application and the Web server. In my case, I have an XML application server that runs on a machine locked away in a computer room. I needed the ability to remotely manage this server, so I dropped a TServerSocket into the application, added some functionality to make it use the HTTP protocol, and viola—my application server now has a Web interface!
The HTTP protocol was invented back in 1990 by Tim Berners-Lee, a British researcher at the CERN particle physics lab in Switzerland. HTTP defines the format of messages sent over the Web. There are two types of HTTP messages—the request and the response. Both the request and response consist of two parts, the message header and the message body. For example, when you request the URL
http://www.bridgespublishing.com/default.htm
in your browser, it sends the following message to the server:
GET /default.htm HTTP/1.1
Accept: /
Accept-Language: en-us
User-Agent: Mozilla/4.0 (
compatible; MSIE 5.5; Windows NT 5.0)
Most browsers also include additional information in the header. For more information on everything that can appear in a HTTP header, refer to www.w3.org.
For the purposes of this article, we’re only concerned with the first line of the header message, the GET. The GET line has the following format:
GET {resource identifier} HTTP/{version}
The resource identifier is typically a file located on the Web server, such as "index.html". It may also be something that tells the server to generate a dynamic page, such as "mypage.asp?param=abc". The version tells the server what version of HTTP is being used.
If the server returns HTML that contains references to other files on the server, the client must send a separate HTTP GET for each one. For example, if an HTML page is returned that has three images on it, a Web browser will then send three HTTP GET messages to the server, one for each image.
A Web server’s primary job is to take the resource identifier passed in the GET message, generate some HTML (or image data), and send the data back as a response. Here is the format of an HTTP response:
HTTP/1.0 {result code} {result message}
MIME-version: 1.0
{message body}
HTTP headers are always terminated with two line breaks. That’s why there’s an extra line before the message body. Some of the common result codes and messages are shown in Table A.
Result Code |
|
|
200 |
|
|
|
|
|
404 |
|
|
The HTTP message shown below is a typical response from a Web server:
HTTP/1.0 200 OK
MIME-version: 1.0
Content-type: text/html
<HTML><HEAD><TITLE>Hello</TITLE></HEAD>
<BODY>Hello from the web server</BODY>
The content-type identifier tells the client (the Web browser) what kind of data is in the message body. The three most common types are "text", "image" and "application". The content type is followed by an additional identifier that describes the file type. For example, "text/html" indicates that the message contains text that should be interpreted as HTML. The content type "image/jpeg" means that the message will contain binary data representing a jpeg image. The "application" type specifies that the message body contains data formatted for an external application, such as .pdf or .doc files.
Now we know everything we need to about HTTP to implement a simple Web server. Luckily, all of the hard work involving sending data over TCP/IP sockets is handled by TServerSocket. We only need to add the code to tell it how to interpret GET messages, and send back the proper results.
There are two properties of TWebServer that are used to configure its operation. WebDir specifies the Web server’s "root" directory. This is the top-level directory where it will look for files. Port specifies the TCP/IP port that the Web server will communicate on. The standard port for HTTP is 80, although you can use a different port number. If you use a port other than 80, then the port number must be specified in the browser URL, such as http://www.server.com:1234 for port 1234.
Using TServerSocket is as simple as setting its port number and then setting the Active property to true. At that point, your computer is a server. The OnClientRead event gets fired whenever a client sends data to the TCP/IP port on the server. The event names can be slightly confusing—OnClientRead happens when a client sends data to the server, and OnClientWrite happens when the server writes data to the client. In this application, only the OnClientRead event is used.
The TWebServer class responds to the OnClientRead event with its ProcessRequest() method. First, it parses the message header to see if it is a HTTP GET message. If it is, it tries to find the file specified by the resource identifier. If the file is found, it responds back with the file’s contents. If the file doesn’t exist, it responds back with a "404 NOT FOUND" message. Finally, if the message is not a HTTP GET message, it responds back with a "400 BAD REQUEST" message.
Note that the GenerateHTML() method is a virtual function, so it can be implemented differently in classes derived from TWebServer. This way, additional functionality can be implemented, such as the ability to parse parameters out of request strings.
Listing A shows how to create an instance of TWebServer. To test it out, start the server and then point your browser to http://localhost. If you use a port other than 80, you must use http://localhost:port.
By itself, the TWebServer class doesn’t provide much utility, except as an extremely lightweight Web server. But classes derived from TWebServer can implement their own version of GenerateHTML() to dynamically create Web pages by using a file as a template, parsing parameters from the request string, and so on.
Now you’ve seen how TServerSocket can be used to add a Web interface to any application just by adding some code to make it use the HTTP protocol. The code for TWebServer and TWebServerApp is shown in Listings B and C and is also available at www.bridgespublishing.com.
Listing A: WebServerMain.cpp
#include <vcl.h>
#pragma hdrstop
#include "WebServerMain.h"
#include "WebServer.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TWebServerMainForm *WebServerMainForm;
__fastcall TWebServerMainForm::TWebServerMainForm(
TComponent* Owner) : TForm(Owner)
{
WebServer = new TWebServer(this);
}
void __fastcall TWebServerMainForm::btnStartClick(
TObject *Sender)
{
if (btnStart->Caption == "&Start") {
WebServer->WebDir = edtWebdir->Text;
WebServer->Port = edtPort->Text.ToInt();
WebServer->Start();
btnStart->Caption = "&Stop";
edtStatus->Text = "Running";
}
else {
WebServer->Stop();
btnStart->Caption = "&Start";
edtStatus->Text = "Stopped";
}
}
Listing B: WebServer.h
#ifndef WebServerH
#define WebServerH
#include <ScktComp.hpp>
class TWebServer : public TComponent
{
protected:
TServerSocket* ServerSocket;
AnsiString GetContentType(AnsiString sFile);
virtual bool GenerateHTML(
AnsiString sResource, TMemoryStream* Output);
void __fastcall ProcessRequest(
TObject *Sender, TCustomWinSocket *Socket);
public:
__fastcall TWebServer(TComponent* AOwner);
AnsiString WebDir;
long Port;
bool Start();
void Stop();
};
#endif
Listing C: WebServer.cpp
#include <vcl.h>
#pragma hdrstop
#include "WebServer.h"
#pragma package(smart_init)
// This is the set of known graphic file types
// for the web server.
bool IsImageType(AnsiString sFileExt)
{
return
(!sFileExt.AnsiCompareIC("jpg") ||
!sFileExt.AnsiCompareIC("jpeg") ||
!sFileExt.AnsiCompareIC("gif") ||
!sFileExt.AnsiCompareIC("bmp") ||
!sFileExt.AnsiCompareIC("png"));
}
__fastcall TWebServer::TWebServer(
TComponent* AOwner) : TComponent(AOwner)
{
ServerSocket = new TServerSocket(this);
ServerSocket->Active = false;
ServerSocket->Name = "ServerSocket";
ServerSocket->ServerType = stNonBlocking;
ServerSocket->OnClientRead = ProcessRequest;
Port = 80;
}
bool TWebServer::Start()
{
if (ServerSocket->Active) {
return false;
}
try {
// Assign TCP/IP Port to use
ServerSocket->Port = Port;
// Start the server
ServerSocket->Active = true;
}
catch (Exception& e) {
MessageBox(NULL, e.Message.c_str(),
"Error Starting Server",MB_ICONEXCLAMATION);
ServerSocket->Active = false;
}
return ServerSocket->Active;
}
void TWebServer::Stop()
{
// Stop the server
ServerSocket->Active = false;
}
AnsiString TWebServer::GetContentType(
AnsiString sFileExt)
{
// Default to text content type
AnsiString sType = String(
"Content-type: text/") + sFileExt;
if (IsImageType(sFileExt)) {
// File is an image
sType =
String("Content-type: image/") + sFileExt;
}
if (!sFileExt.AnsiCompareIC("zip") ||
!sFileExt.AnsiCompareIC("doc") ||
!sFileExt.AnsiCompareIC("pdf"))
{
// File is an application document
sType = String("Content-type: application/") +
sFileExt;
}
return sType;
}
// This function contains the core functionality
// of the web server. It takes a request and
// generates output to a stream.
bool TWebServer::GenerateHTML(
AnsiString sResource, TMemoryStream* Output)
{
bool bResult = false;
if (FileExists(sResource)) {
// Send the specified file to output stream
Output->LoadFromFile(sResource);
bResult = true;
}
return bResult;
}
void __fastcall TWebServer::ProcessRequest(
TObject *Sender, TCustomWinSocket *Socket)
{
// Read incoming data from the client.
AnsiString sInput = Socket->ReceiveText();
// Put the request into a TStringList, to
// separate the request header lines.
TStringList* Request = new TStringList();
Request->CommaText = sInput;
// Create another TStringList to hold response
TStringList* Response = new TStringList();
// Make sure the request is a HTTP GET. The
// server doesn't support other request types.
if (Request->Strings[0] == "GET") {
// The second item in the request is the
// file/resource to retrieve.
AnsiString sResource = Request->Strings[1];
// Build complete filename for requested file.
AnsiString sRequestedFile = WebDir+sResource;
// If the request ends in a slash, append
// the default filename.
char cEnd =
sRequestedFile[sRequestedFile.Length()];
if (cEnd == '\\' || cEnd == '/') {
sRequestedFile += "index.html";
}
// Get file extension and omit the '.'
AnsiString sFileExt = ExtractFileExt(
sRequestedFile).SubString(2,10);
// Get content type description
AnsiString sContentType =
GetContentType(sFileExt);
// Create an output stream for HTML
TMemoryStream* HTMLData = new TMemoryStream();
AnsiString sHTTPStatus;
// Generate output.
if (GenerateHTML(sRequestedFile, HTMLData)) {
sHTTPStatus = "HTTP/1.0 200 OK";
}
else {
sHTTPStatus = "HTTP/1.0 404 NOT FOUND";
}
// Create response header
Response->Add(sHTTPStatus);
Response->Add("MIME-version: 1.0");
Response->Add(sContentType);
Response->Add("");
// Send response header
Socket->SendText(Response->Text);
// Send HTML
if (HTMLData->Size > 0) {
Socket->SendBuf(
HTMLData->Memory, HTMLData->Size);
}
else {
// If the requested file is not an image,
// return an HTML page indicating bad
// filename. Otherwise, just return header.
if (!IsImageType(sFileExt)) {
Response->Add("<HTML><HEAD><TITLE>File "
"Not Found</TITLE></HEAD>");
Response->Add(
"<BODY><H1>File Not Found</H1>");
Response->Add(
"The document you requested: ") ;
Response->Add(Request->Strings[1]);
Response->Add(
"<BR>was not found.</BODY></HTML>");
Socket->SendText(Response->Text);
}
}
delete HTMLData;
}
else {
// Invalid request type.
Response->Add("HTTP/1.0 400 BAD REQUEST");
Response->Add("MIME-version: 1.0");
Response->Add("Content-type: text/html\n\n");
Response->Add("<HTML><HEAD><TITLE>Invalid "
"Request Type</TITLE></HEAD>\n");
Response->Add(
"<BODY><H1>Invalid Request Type.</H1>\n");
Response->Add("Request:<BR><PRE>");
Response->Add(sInput);
Response->Add("</PRE></BODY></HTML>");
Socket->SendText(Response->Text);
}
delete Request;
delete Response;
// Done sending
Socket->Close();
}