TCP and UDP Networks


TCP and UDP are both protocols used by the Internet. The most commonly used is TCP, which stands for Transmission Control Protocol. It is used to connect two computers and to reliably transfer data between them. The data itself is transmitted as packets, which are small units of data that can theoretically arrive at the host out of sequence. TCP ensures that they are placed in sequence and arrive reliably. It is used by the most commonly used Internet applicationthe World Wide Web and email.

SocketCore Class

The SocketCore class is an abstract class, meaning that it is never instantiated itself. It serves as the parent class to the classes that you will use when writing programs that interact with networks. The class provides some of the basic features that will be needed by all the subclasses in order to work. For starters, one of the first things you will need to do when creating a network connection is to specify which port to listen to or which port to try to connect to.

In the case of the World Wide Web and the HTTP protocol, the standard port number is 80, but that's not the only port that's available. Port numbers can range from 0 to 65535 and are divided into three tiers. So-called Well-Known Ports are those from 0 to 1023. On most systems, you need to have superuser status to use these ports. The next tier is called Registered Ports, and these number from 1024 to 49151. Finally, Dynamic Ports are numbered from 49152 to 65535.

A list of commonly used ports follows:

FTP                    21 SSH                    22 TELNET                 23 SMTP                   25 HTTP                   80 HTTPS                  443 POP3                   110 HTTP-Alternate         8080 


Although Registered Ports do have a semiformal status, there's no reason you can't use port numbers in that range, especially when you know that it is available. REALbasic provides TCPSocket subclasses for HTTP, SMTP, and POP3, and these subclasses will default to the port that is appropriate for the given protocol.

To find out which port to connect to, or bind to, check the following property:

SocketCore.Port as Integer


Exactly what this means depends on the context. When two computers connect through a network, one plays the role of the server and the other of the client. The server is said to listen on a port, or that it is bound to a port. A client connects to the server through a particular port. As a consequence, if the SocketCore subclass you are working with is a server and is waiting to receive data, the Port property says which port it is listening to. Otherwise, it says which port it is going to try to connect to.

Sending and Receiving Data

Because the SocketCore class is an abstract class, it doesn't implement all the features required to make a network connection. One of the most important things that is missing is a way to read and write data. Rest assured that this functionality is implemented in the subclasses, but you won't find it here.

What you will find, however, is something that is common to all the subclasses, and that is the event that gets triggered whenever data arrives:

SocketCore.DataAvailable


Whenever a socket receives data, the DataAvailable event is triggered. This does not mean, however, that all the data is available. In fact, there is no guarantee that all the required information has been retrieved. Because all the data doesn't come at once, the socket classes all implement two internal buffers, one for data that is coming in and one for data that is being sent out. The process you use to access these buffers differs depending on which subclass you are using. I will look at the buffers in more detail in the section on the TCPSocket class.

In addition to receiving data, sockets can send data. The SocketCore class doesn't implement a way to actually send data. For that, you'll also need to look at the specific subclass in question. However, sending data works like receiving data does in the sense that it does not necessarily get sent all at once. As a consequence, the socket classes all have the following event, which is triggered when all the data in the send buffer has been sent:

SocketCore.SendComplete(UserAborted as Boolean)


When an error is encountered, the Error event is triggered:

SocketCore.Error


To find out which error caused the event to be triggered, you need to check the LastErrorCode property:

SocketCore.LastErrorCode as Integer


The following class constants describe the cause of the error that was just triggered:

SocketCore.OpenDriverError = 100 SocketCore.LostConnection = 102 SocketCore.NameResolutionError = 103 SocketCore.AddressInUseError = 105 SocketCore.InvalidStateError = 106 SocketCore.InvalidPortError = 107 SocketCore.OutOfMemoryError = 108


SocketCore Properties

Every socket is given a unique identifier, which is accessible through the following property:

SocketCore.Handle as Integer


Being able to access the Handle for each socket can be convenient because in many cases, you will have more than one socket open at a time. In the web server sample application, you will see that several sockets are open at any given time, and you can use the Handle to identify which socket is performing any activity.

The following properties perform some basic tasks, which are mostly self-explanatory. To see if you are connected to another socket:

SocketCore.IsConnected as Boolean


To find out what the local IP address is for the machine your application is running on, check

SocketCore.LocalAddress as String


The following property identifies which NetworkInterface your socket is using:

SocketCore.NetworkInterface as NetworkInterface


SocketCore Methods

Sockets open their connection by calling the Connect method, and they can close it by calling the Close method. The subclasses that will be reviewed next often provide a more graceful means of opening and closing sockets.

SocketCore.Close SocketCore.Connect


If you want to purge the internal send buffer, call the following:

SocketCore.Purge


One important thing to remember when dealing with sockets is that the Internet protocol is big endian, like PowerPC Macintosh, so you will need to pay attention to endianness when using sockets.

TCPSocket

Things start to get interesting when you use the TCPSocket class. This is the one that I will be using in the sample web server application.

TCPSocket Events

Although SocketCore has the isConnected property that you can use to check to see if your socket is connected to another socket, the TCPSocket adds an additional layer of functionality by triggering an event when a connection is first established:

TCPSocket.Connected


The SendProgress event is also added to this class, which allows you to track how much of a document has been downloaded:

TCPSocket.SendProgress(BytesSent as Integer, BytesLeft as Integer) as Boolean


If you want to cancel the send, you can return TRue from this event.

The following property tells you what address you are trying to connect to:

TCPSocket.Address as String


The address you actually are connected to after you have connected is not necessarily the same as the one you tried to connect to in the first place. Therefore, REALbasic lets you check the remote address you are connected to using this property:

TCPSocket.RemoteAddress as String


TCPSocket Methods

You can use TCPSocket to both send and receive data. If you are going to be receiving data, you will need to tell TCPSocket to Listen on a given port for any requests using the following method:

TCPSocket.Listen


If a connection has already been established, you can close it using the following:

TCPSocket.Disconnect


The SocketCore class itself did not provide any methods for reading or writing data. Because the whole point of using sockets is to send and receive data, every subclass provides its own methods for doing so. The TCPSocket provides the following three methods for reading data in the receive buffer:

TCPSocket.Read(Bytes as Integer, [Encoding as TextEncoding]) as String TCPSocket.ReadAll([Encoding as TextEncoding]) as String TCPSocket.Lookahead([Encoding as TextEncoding]) as String


Under normal circumstances, when you read the buffer, the data is cleared from the buffer, regardless of whether you call Read or ReadAll. There are times when you want to see what's in the buffer without deleting the contents of the buffer, and that is when you call the Lookahead method. You'll see an example of this in the web server code supplied later in this chapter.

In addition to reading data, you can write data as well, simply by calling the following method:

TCPSocket.Write(Data as String)


Keep in mind that you can send a string of any size to the Write method, but if the String is particularly long, it will not get sent all at once and will take some time to be completely sent. In the meantime, your application can check the data passed in the SendProgress event to monitor progress.

HTTPSocket Inherits TCPSocket

The HTTPSocket class is used to handle the client side of an HTTP connection. Because HTTP (Hypertext Transfer Protocol) is the protocol used by the World Wide Web, in simpler terms this means that the HTTPSocket class performs the role of the web browserin the sense that it can request files from a web server and receive them. Any kind of file can be retrieved this way, but html files are most commonly associated with the web. Note that the HTTPSocket class is a networking class. It cannot display html pages; that's what the HTMLViewer control is for.

HTTPSocket is a subclass of TCPSocket and does all the same things that TCPSocket does. The layer of functionality it adds is that of creating HTTP requests, which have a specific format, and it handles retrieving the data that is sent back as a result of the request.

The HTTP protocol supports several types of requests, but I will concern myself with two in particular, a GET request and a POST request. The most common kind of request is a GET, which does what the name impliesit makes a request for a certain file and the HTTP server returns the file. Here is an example of the body of a GET request:

GET / HTTP/1.1 Accept: */* Accept-Language: en-us Accept-Encoding: gzip, deflate Cookie: photo_display=thumbnail; nde-textsize=16px; SessionID=12345 User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us)         AppleWebKit/412 (KHTML, like Gecko) Safari/412 Connection: keep-alive Host: localhost:8080


The first line of the request tells you what kind of request it is and what the request is for. It consists of a single line that is delimited by two spaces, like so:

GET<space>/path/to/file<space>HTTP/1.1


Following the initial line are a series of key/value pairs that provide basic information about the request, and at the very end of the request (not visible in the example) is a blank line that signifies the end of the request. This is important! A line ending according to the HTTP protocol consists of a carriage return and a linefeed, which is the same as the value for EndOfLine.Windows.

A POST request is similar, except that it also sends additional information. The term "POST" refers to the idea of POSTING information and is used when the user has filled out a form and submitted it in the web browser. A sample of a POST request can be seen in Listing 9.2.

[View full width]

POST /form.html HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.6) Gecko/20040113 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9, text/plain;q=0.8 ,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/form.html Content-Type: application/x-www-form-urlencoded Content-Length: 28 textfield=Mark&Submit=Submit


The first line is different, as you can see, because it designates the request as a POST. You will also notice that there are some additional key/value pairs that are being sent, the most important of which are Content-Type and Content-Length. Both of these are always available on POST requests. Just as in GET, the main request is ended with a blank line, but in addition to the blank line, a POST also attaches additional information. In the preceding example, the post has appended data from a form that the user has filled out.

Even though a POST is intended to be used for sending data to the web server, it is not the only way. You can use a GET request, too. The only difference is that the encoded data is not sent in the header; it's appended to the URL. The following GET request shows what a GET request looks like:

[View full width]

GET /form.html?textfield=Mark&Submit=Submit HTTP/1.1 Accept: */* Accept-Language: en-us Accept-Encoding: gzip, deflate Cookie: photo_display=thumbnail; nde-textsize=16px; SessionID=12345 User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412 (Khtml, like Gecko) Safari/412 Connection: keep-alive Host: localhost:8080


In the first line of the request, everything that follows the "?" in the string of characters in /form.html?textfield=Mark&Submit=Submit is the data that is sent by a POST in the body of the request. As you will see in the example, when you create a form using html, you can specify which kind of request that form should generate, either a GET or a POST.

POST /form.html HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.6) Gecko/20040113 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9, text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/fileform.html Content-Type: multipart/form-data; boundary=myBoundaryValue Content-Length: 59628 --myBoundaryValue Content-Disposition: form-data; name="datafile"; filename="05-Threading.doc" Content-Type: application/msword <lots of data> 


The example I have provided will accommodate both kinds of POST requests. When submitting a file, the file is submitted according to its MIME type. This is designated in the Content-Type field. There are two values submitted in Content-Type. The value "multipart/form-data" indicates that a file is being uploaded and that "boundary=..." is a value used to delimit the file. The uploaded file starts with the string "" followed by the data that was passed as the boundary value. The file can be divided up into multiple parts, and each part is separated by this string of characters, which in the preceding example is

--myBoundaryValue


The end of the document is signified by the boundary value again, this time with "" on either side, like so:

--myBoundaryValue--


Figure 9.2 is an example of a html form:

Figure 9.2. Sample form.


You can code this form in html either as a POST or as a GET. Either way, the form looks the same. To create it as a POST, you would write the following:

<form name="form1" method="post" action="http://localhost:8080/form.html">       <p>            <input type="text" name="textfield">     </p>       <p>            <input type="submit" name="Submit" value="Submit">     </p> </form> 


The request would be the POST request listed previously. To create it as a GET, it would look like this:

<form name="form1" method="get" action="http://localhost:8080/form.html">     <p>        <input type="  text" name="textfield">     </p>     <p>        <input type="submit" name="Submit" value="Submit">     </p> </form>


The headers for this request would be like the GET request I just listed.

You can use the HTTPSocket class to perform any kind of request. You can even use it to mimic having filled out a form. You can use either a GET or a POST request.

An html form passes a series of key/value pairs, and the HTTPSocket class makes it easy to submit the values needed using a dictionary. If you are going to use a GET request, you can use the following method to take the values in a dictionary and return a URL-encoded String:

HTTPSocket.EncodeFormData(Form as Dictionary) as String


Because this returns a String, you would use this by appending a "?" following by the returned String to the URL, something like this:

Dim aForm as New Dictionary aForm.Value("textfield") = "Mark" HTTPSocket1.Get("http://localhost:8080/form.html" + "?" + _ HTTPSocket1.EncodeFormData(aForm)


You can do something similar with a POST, using the following method:

HTTPSocket.SetFormData(Form as Dictionary)


The code would look like the following:

Dim aForm as New Dictionary aForm.Value("textfield") = "Mark" HTTPSocket1.SetFormData(aForm) HTTPSocket1.Post("http://localhost:8080/form.html")


In addition to posting data from html forms, POST requests can also be used to upload files. The HTTPSocket class has a method to do this:

HTTPSocket.SetPostContent(Content as String, ContentType as String)


Authentication

The HTTP protocol defines two means of authenticating users: Basic Authentication and Digest Authentication. The most widely used method is Basic Authentication, which is what REALbasic supports, with the following event:

[View full width]

HTTPSocket.AuthenticationRequired(Headers as InternetHeaders, ByRef Name as String, ByRef Password as String) Handles Event


It works like this: When you make a request for a web page, the web server checks to see if authentication is required. If it is, it sends back a notice saying that it needs to get a username and a password. When this happens, the HTTPSocket class triggers the AuthenticationRequired event and passes along the headers so that you can glean any necessary information from them.

As you can see, it also passes a Name and Password variable ByRef. Although this is an unusual occurrence, it means that you should assign a value for the Name and Password parameters. When you do, the server will be sent the username and password to be authenticated.

Get, Post, and GetHeaders Methods

There are several request types that are legal HTTP requests, but the HTTPSocket class implements the three that are required: GET, POST, and HEAD, represented by the Get, Post, and GetHeaders methods. Get and Post each come in three forms, as follows:

HTTPSocket.Get(URL as String) HTTPSocket.Get(URL as String, File as FolderItem) HTTPSocket.Get(URL as String, Timeout as Integer) as String HTTPSocket.Post(URL as String) HTTPSocket.Post(URL as String, File as FolderItem) HTTPSocket.Post(URL as String, Timeout as Integer) as String HTTPSocket.GetHeaders(URL as String) HTTPSocket.GetHeaders(URL as String, Timeout as Integeer) as String


If you want to get the home page of REAL software, you would execute the request like so:

httpsocket1.Get "http://www.realsoftware.com/"


The first two method signatures are subroutines because they do not return a value. Because of the asynchronous nature of network communications, an event is triggered when the data is returned. Two events are relevant:

[View full width]

HTTPSocket.PageReceived(URL as String, HTTPStatus as Integer, Headers as Dictionary, Content as String) Handles Event HTTPSocket.DownloadComplete(URL as String, HTTPStatus as Integer, Headers as Dictionary, File as FolderItem)Handles Event


The event to use is contingent on which form of the Get and Post methods is used. You have the option of passing a FolderItem when calling the methods, and if you do, the data that you have requested will be made available in the DowndloadComplete event. If you do not pass a FolderItem, the content will be made available as a String in the PageReceived event. The general rule is this: if you are requesting an html or XML file, do not pass a FolderItem. If you are downloading a binary file, such as a Microsoft Word document or a JPEG image, pass a FolderItem in the method. GetHeaders works like a GET request, expect that only the headers are returned, not the requested file.

You also have the option of passing a "Timeout" value in the Get, Post, and GetHeaders methods, which makes the request run in synchronous mode, which means that the methods are functions. REALbasic will block and wait for a result, which will be in the form of a String. When using the class in synchronous mode, you can also set the following parameter to true to yield time that allows other events to be triggered:

HTTPSocket.Yield as Boolean


If you are using synchronous mode, you can access headers using the following property:

HTTPSocket.PageHeaders as InternetHeader


TCPSocket has a SendProgress property, and HTTPSocket adds a ReceiveProgress property to the mix:

HTTPSocket.ReceiveProgress(BytesReceived as Integer, TotalBytes as Integer) Handles Event


You have seen this used in an earlier example with a ProgressBar control.

You have already seen examples of HTTP headers, but you haven't seen all the different variations yet (at least not in this book). The great thing about the HTTP protocol is that it is very flexible. One thing it allows you to do is to create your own headers. This can be a very powerful tool in your programming belt if you want to develop custom HTTP services. You can arbitrarily set any header using the following method:

HTTPSocket.SetRequestHeader(Name as String, Value as String)


You can also clear all your headers by calling

HTTPSocket.ClearRequestHeaders HTTPSocket.HeadersReceived(Headers as InternetHeaders, HTTPStatus as Integer) Handles Event


When you make an HTTP request and get the results, the HTTP server has to send back a status code. Hopefully, the status code will be 200, which means that everything is okay. If you are using the HTTPSocket class in synchronous mode, you can find out what the status code was by checking the following property:

HTTPSocket.HTTPStatusCode as String


Here is an abbreviated list of sample codes:

200 - OK 201 - Created 202 - Accepted 203 - Non-Authoritative Information 204 - No Content 205 - Reset Content 206 - Partial Content 300 - Multiple Choices 301 - Moved Permanently 302 - Found 303 - See Other 304 - Not Modified 305 - Use Proxy 307 - Temporary Redirect 400 - Bad Request 401 - Not Authorised 403 - Forbidden 404 - Not Found 408 - Request Timeout 409 - Conflict 410 - Gone 411 - Length Required 412 - Precondition Failed 414 - Request URI Too Long 415 - Unsupported Media Type 500 - Internal Server Error 501 - Not Implemented 502 - Bad Gateway 503 - Service Unavailable 504 - Gateway Timeout 505 - HTTP Version Not Supported


There are some situations where users sit behind a firewall and aren't given direct access to the Internet. This is often done at businesses as a security measure.

HTTPSocket.HTTPProxyAddress HTTPSocket.HTTPProxyPort


InternetHeaders Class

The InternetHeaders class is basically an array of key/value pairs, plus some methods that make it easier to add and delete individual pairs in the list.

From the example given earlier, the source of the header data looks like this:

Accept-Language: en-us Accept-Encoding: gzip, deflate Cookie: photo_display=thumbnail; nde-textsize=16px; SessionID=12345 User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412  (Khtml, like Gecko) Safari/412 Connection: keep-alive Host: localhost:8080


You can access the raw source by calling the following method:

InternetHeaders.Source as String


Using the same header data just listed, I could get access to the "Connection" value like this:

Dim s as String s = Headers.Value("Connection")


I can use the following method to find out how many headers there are:

InternetHeaders.Count as Integer


This means that I can get access to the value of any given header by using either the name, the index, or both, using the following methods:

InternetHeaders.Value(Name as String) as String InternetHeaders.Value(Name as String, Index as Integer) as String InternetHeaders.Value(Index as Integer) as String InternetHeaders.Name(Index as Integer) as String 


Likewise, I can delete individual headers using either the name or the name and the index:

InternetHeaders.Delete(Name as String) InternetHeaders.Delete(Name as String, Index as Integer) InternetHeaders.DeleteAllHeaders


If I want to add an entirely new header to the object, I call the AppendHeader method:

InternetHeaders.AppendHeader(Name as String, Value as String)


If I want to modify an existing header, or add a new one if it doesn't exist, I can call the SetHeader method:

InternetHeaders.SetHeader(Name as String, Value as String)


From CGI to HTTPServer

One of the challenges of working with sockets is the fundamentally asynchronous nature of computer networks. There is almost always some kind of network latencya period of time between when a request is made and when an answer is received. More importantly, a request may not arrive complete. Rather, it will arrive in batches and your program needs to know what to expect and how to gather up all the data prior to acting on it.

You've seen an example of the HTTPSocket class in the RSSReader application, but we cannot use it for a web server because the HTTPSocket class plays the role of the client. To create a web server, you will need to use the TCPSocket class, which is the super class of HTTPSocket.

A web server is used to view web pages, and a web page is created using html. Here's an example of a page that I will view in the sample application:

[View full width]

<!DOCTYPE html PUBLIC "-//W3C//DTD html 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>REALbasic HTTPServer</title> </head> <body> <h1>REALbasic Cross-Platform Application Development</h1> <h4>Mark S. Choate</h4> <img src="/books/2/97/1/html/2/images/REALbasicBook.jpg" align="left" /> <p><i>REALbasic Cross-Platform Application Development</i> covers the most recent version of REALbasic, known as REALbasic 2005.</p> <p>Known for its ease-of-use, REALbasic is also a powerful language that compiles native applications for Windows, Macintosh and Linux operating systems. It's simple enough for a beginner, but powerful enough to meet the needs of seasoned programmers as well.</p> </body> </html>


Figure 9.3 shows what this page looks like rendered in a web browser:

Figure 9.3. Web browser view.


One thing that you should notice about this file is that the references to images include URLs to the images, not the image itself. This means that when you access a page, you generate a request for the page itself, plus requests for all the images that are referenced within the html of the page request. When the browser gets the source html page, it then goes through and requests all the individual images. This means that a web server needs to be prepared to handle a lot of concurrent requests. If I wrote the server using TCPSocket alone, I would run into a problem because each new request needs a new socket connection (the HTTP/1.1 protocol lets you keep the socket connection open and make multiple requests through the same connection, but this sample closes the socket connection after each individual request for simplicity's sake).

To accommodate this, you need a way to manage a pool of sockets that are available for requests, a way to hand off requests to these sockets when they come in, and a way to create new sockets if all the current sockets are being used. Fortunately, REALbasic provides a class that does just that for you.

ServerSocket Class

The ServerSocket class manages a pool of sockets for you, and this is what I will use in the web server.

The ServerSocket class is one of those classes that will save you hours of coding if you do much with sockets. Not only does it handle one of the most challenging aspects of working with sockets, which is managing a pool of them to handle multiple requests, it also does it in a way that is remarkably intuitive and easy to use.

To use it, the ServerSocket instance must be persistent. You can use it like a Control by dragging it onto a Window, or create it as a property of a Window or App object. Whichever you do, the important part is that it persists because this one object will be listening for requests and then spinning them off throughout the time the server is running.

There are a few properties to set at first. The first, which should come as no surprise, is setting the port to listen to

ServerSocket.Port as Integer


Then you also need to tell the object some information about how to handle socket creation. Because some overhead is associated with instantiating a new socket when a request is made, it's a good idea to have some sockets ready and waiting for the next request. You can set the following property to determine the minimum number of sockets to be instantiated when listening for requests:

ServerSocket.MinimumSocketsAvailable as Integer


The flip side of this is that you also want to set a limit as to how many sockets can get created. If all of a sudden the server is handling thousands of requests, you could quickly run out of memory from spinning off too many sockets. You can set this limit with the following property:

ServerSocket.MaximumSocketsConnected as Integer


There are two other properties that can be useful:

ServerSocket.IsListening as Boolean ServerSocket.LocalAddress as String


If you want to check to see if the ServerSocket instance is listening for connections, check the IsListening property. Also, if you want to know what the local IP address is, you can check LocalAddress.

After the necessary properties are set, you can tell the ServerSocket instance to start listening for connections by calling the Listen method:

ServerSocket.Listen


As you might expect, you can tell it to stop by calling this method:

ServerSocket.StopListening


Everything that I've covered so far composes the "easy part." All the heavy lifting is done in the AddSocket event:

ServerSocket.AddSocket() as TCPSocket Handles Event


"Heavy lifting" is actually overstating things a bit. The AddSocket event is triggered when a request comes in on the port that the ServerSocket is listening to. You need to write the code in this event that defines how you respond to the request. The response is to instantiate a socket of some sort and then go back to listening for more new requests. The socket you instantiate at this point is the socket that will respond to the request.

If something goes wrong in the process, the ServerSocket error event is triggered:

ServerSocket.Error(ErrorCode as Integer) Handles Event


The HTTPServer Application

To implement the web server, I will create a subclass of ServerSocket called HTTPServer and create a subclass of TCPSocket called HTTPConnection. This will be the socket I instantiate when a new request comes in. First, take a look at the HTTPServer class to see how new instances of HTTPConnection are handled.

Class HTTPServer Inherits ServerSocket

The HTTPServer class is simple. There is one property:

DocRoot As FolderItem


This property refers to the root directory that will be used by the web server. When a request for a page is received, the socket will look for the file within the DocRoot FolderItem.

In addition to the DocRoot property, the subclass also includes an implementation of the AddSocket event, which follows.

Listing 9.1. Function AddSocket() As TCPSocket Handles Event

Dim tcp as HTTPConnection // A request for a page has been received and // a new socket needs to be instantiated to // handle it. tcp = New HTTPConnection(DocRoot) // Send information to the server window window1.AppendString("Adding socket") // Return the socket Return tcp 

In the sample application, the HTTPServer instance is a property of Window1. Window1 has an EditField, where messages about the current state or activity of the server are displayed. It also has a PushButton labeled Request that will be used to stimulate several simultaneous requests, but don't worry about that at this point.

When the HTTPServer class is first instantiated, it opens up a pool of sockets. In the sample application, each time a socket is instantiated, it is displayed in the Window. You can see an example in the following figure:

Figure 9.4. Pool of sockets.


In the Open event of Window1, the HTTPServer instance is instantiated and assigned to the Window1.Server property. In addition to being instantiated, the values are set for the minimum and maximum number of sockets that are available. The port is set to 8080, which is commonly used as an alternative to the standard port 80 for web servers. A lot of applications that are servers use 8080, so a person can try out the application without interfering with a real web server running on that computer.

Listing 9.2. Sub Window1.Open() Handles Event

Server = new HTTPServer Server.DocRoot = getFolderItem("DocRoot") Server.MinimumSocketsAvailable =5 Server.MaximumSocketsConnected =10 Server.Port = 8080 Server.Listen 

In addition to the Open event, Window1 also implements the AppendString method, which is a simple way of displaying output to the window as the application is running.

Listing 9.3. Sub Window1.AppendString(s as String)

Window1.EditField1.SelStart = Len(Window1.EditField1.Text) Window1.EditField1.SelText = s + EndOfLine 

Handling GET Requests

So far, you've seen what happens when the application is first run and the initial sockets are created. What is more interesting is to see how the application responds to actual HTTP requests. We do not need a web browser to generate those requests because we can do it programmatically with REALbasic.

In the sample application, there are four HTTPSockets that can be called to make HTTP requests against the web server. All I did was drag them onto Window1. If you click the Request button, they are all triggered as close to simultaneously as possible. Figure 9.5 shows which socket received which request, and is identified by the TCPSocket.Handle property:

Figure 9.5. Request button output.


Control Window1.PushButton1:
Listing 9.4. Sub PushButton1.Action() Handles Event

HTTPSocket1.Get("http://localhost:8080/index.html") HTTPSocket2.Get("http://localhost:8080/index.html") HTTPSocket3.Get("http://localhost:8080/index.html") HTTPSocket4.Get("http://localhost:8080/index.html") 

Now if you start the application and click the Request button, this is the output that you will see:

At this point, you don't really need to worry too much about what's happening, except to understand that whenever a socket receives a request, the DataAvailable event is triggered. In the HTTPConnection class, I have this line of code that displays in the Window the Handle for each socket that has received a request:

Window1.AppendString("Socket " + str(me.Handle) + ": Data available")


In the previous figure, clicking the Request button and generating four simultaneous events caused four different sockets to respond with handles 20, 22, 24, and 21, respectively. With this information, you can be assured that the HTTPServer is properly getting requests and handing them off to different sockets.

At the beginning of this section, I shared a sample html page and showed what the page would look like when displayed in a web browser. If I call this page index.html and place it as a text file in the DocRoot FolderItem (which is a property of the HTTPServer object), I can access it using my web browser by typing in the following URL:

http://localhost:8080/


When I try to access this page through my browser, the browser shows the page, and the web server application displays the following information:

Socket 16: Data available Path: / Socket 16: Data available Path: /images/REALbasicBook.jpg


You can see that this page generated two requests. The first request was for the path / and the second was for /images/REALbasicBook.jpg. The / is the root file, and the default name for that file is index.html, so the application automatically looks for a file named index.html if there is no file listed.

If you go back and examine the original html file, you will see the following in the code:

<img src="/books/2/97/1/html/2/images/REALbasicBook.jpg" align="left" />


This is the html tag that is responsible for the second request. As you might imagine, a single page can generate quite a few requests, depending on how many images are on the page. This example is interesting because the same socket handled both requests.

At this point, you may want to understand just how the HTTPConnection socket sent the html page and the image to the web browser, but before I go into the code details, I want to show you a few other examples as well.

Handling POST Requests: Posting Forms and Transferring Files

Following is an example of how the application handles a form. Here is the html used to create the form (I have named the file form.html and saved it in the DocRoot):

<!DOCTYPE html PUBLIC "-//W3C//DTD html 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>HTTPServer Form</title> </head> <body> <p><b>Tell me your name:</b></p> <form name="form1" method="post" action="http://localhost:8080/form.html">   <p>       <input type="text" name="textfield">   </p>   <p>       <input type="submit" name="Submit" value="Submit">   </p> </form> </body> </html>


When displayed, it looks like Figure 9.6:

Figure 9.6. Form.


If you fill out this form using your web browser and click Submit, the page shown in Figure 9.7 is returned as the result:

Figure 9.7. Hello, Mark results.


The form generated a POST request, which was processed by the web server application. The value that was filled in on the form was used to compose the output, which in this case was "Hello, Mark".

Although a POST is the most common way to handle form data, you can also use a GET request as well. When you do, instead of having the POST data appended to the request, the data from the form is appended to the URL itself. The following example shows what happens when the following URL is used to access the form.html document:

http://localhost:8080/form.html?dog=lucy


The sample app takes the data after the "?" character, parses it, and responds thus:

Figure 9.8. Get with an extended URL.


In both of these examplesthe POST and the GET requestthe sample application checks the values that are sent to it and responds accordingly. When you read through the sample code later on in the chapter, you will see exactly how it does this.

Uploading a File

The previous two examples used form data of some sort to generate a request. You can also use a POST to upload a file. Here is the html for a page that will let you upload a file to the web server application:

<!DOCTYPE html PUBLIC "-//W3C//DTD html 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html>      <head>         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">         <title>HTTPServer Form</title>      </head>      <body>      <p><b>Please specify a file, or a set of files:</b></p>      <form name="form1" method="post" enctype="multipart/form-data" action="http://localhost:8080/form.html">        <p>      <input type="file" name="datafile" size="40">      </p>      <p>      <input type="submit" value="Send">      </p>      </form>      </body> </html>


In the examples, I have named this file fileform.html. There are two distinguishing characteristics to the html used to create this form. First, there is a value of "multipart/form-data" for the enctype attribute. This tells the server that a file is being uploaded and how to handle it. There is also an input element whose type is "file" in contrast to "text" as in the previous example.

Figure 9.9 shows you what this form looks like when viewed in a web browser:

Figure 9.9. Form for uploading a file.


Next is an excerpt from the request that is generated when this form is filled out:

[View full width]

POST /form.html HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.6) Gecko/20040113 Accept: text/xml,application/xml,application/xhtml+xml, text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/fileform.html Content-Type: multipart/form-data; boundary=--------------------------- 3704495525905278111478986690 Content-Length: 59628 -----------------------------3704495525905278111478986690 Content-Disposition: form-data; name="datafile"; filename="Threading.doc" Content-Type: application/msword <lots of data> -----------------------------3704495525905278111478986690--


In this example, I uploaded a Microsoft Word document called Threading.doc. All the data in the document isn't shown; for brevity's sake I replaced it with the text <lots of data>.

Now you have seen examples of several kinds of requests and how the web server application handles them. The interesting part is seeing exactly how it gets done. There are two additional classes HTTPConnection, which is a subclass of TCPSocket, and HTTPRequest, which is a subclass of Dictionary, that are used to process the request. The hard part is that a request does not come in all at once. You may get it in small segments, with each instance triggering a DataAvailable event in the HTTPConnection object. Not only do you need to wait until you have the entire request, you also need to see what kind of request it is and respond accordingly. To make this happen, take the following steps:

  1. Don't do anything until you receive a blank line. Then you know you have the primary information about the request.

  2. If it's a GET request, you can go ahead and respond.

  3. If it's a POST, you may need to wait for additional data. You will want to know what kind of data to expect.

  4. If the value of Content-Type is "application/x-www-form-urlencoded", you can expect plain old form data. If the value is "multipart/form-data", then you can expect a file. The header should also have a Content-Length value. This can be used to make sure you have all the form data you need.

  5. If a file is being uploaded, you will be given a MIME type and a string of characters that serve as a delimiter of the file being uploaded, and you can use that to determine when the upload is complete.(It also passes the Content-Length value.)

All of this is managed by the HTTPConnection class, instances of which are the sockets spun off by the HTTPServer object.

Class HTTPConnection Inherits TCPSocket

The HTTPConnection class is a subclass of TCPSocket rather than the HTTPSocket class because the HTTPSocket class is designed to be the client, or requesting socket, rather than the serving socket. It has three properties:

Request As HTTPRequest Stream As BinaryStream Protected DocRoot As FolderItem


The request object holds the values that are passed in the header, including data uploaded from forms. The response is written to the client using a BinaryStream object, and the DocRoot FolderItem has already been discussed. This is the root folder that all requests are considered relative to.

Listing 9.5. Sub HTTPConnection.Constructor(aDocRoot as FolderItem)

// Pass the DocRoot in the constructor since // an HTTP Server must have a root document // to function correctly. DocRoot = aDocRoot

It is in the DataAvailable event that everything happens. The code is heavily commented, so you can read through it to see what is happening.

Listing 9.6. Sub HTTPConnection.DataAvailable() Handles Event

[View full width]

Dim data as String Dim headers(-1) as String Dim KeyVal(-1) as String Dim f as FolderItem Window1.AppendString("Socket " + str(me.Handle) + ": Data available") // Instantiate an HTTPRequest object. // The reason I test for Nil is that this event will // likely be called more than once for each request. // If the request object is Nil, then I know that it // is the first time the event has been called and // that I need to instantiate a new object. If me.request = Nil Then     me.request = new HTTPRequest(DocRoot) end if // Take a look at your data - but use Lookahead so // that the data remains in the buffer. Later on it // will be easier to handle if it is all in one place. data = me.Lookahead // The first part of the header is delimited by a blank line. // If it is a GET request, then you have the complete request. // If it is a POST, then you will have additional data coming // after the blank line. I don't do anything with the request // until I find the blank line. If (Instr(Data, EndOfLine.Windows + EndOfLine.Windows) > 0) Then     // I have a blank line, which means I have the complete header,     // unless it is a POST. If the value for request.type has     // not been set, then I parse the header data in order to     // generate values for the request object. If the type property     // is not empty, I don't do anything, because I know that the     // header has been parsed. When it is not empty, chances are     // the request is a POST and a lot of data is being posted.     If me.request.type = "" Then         parseHeader(data)     End If End If // Test to see if it is a POST request If me.request.type = "POST" Then     Window1.AppendString("Type: POST")     // POST requests are usually generated as a consequence     // of filling out a form. One special form element that     // requires special handling is the one that allows the     // user to upload a file to the server. If a file is being     // uploaded to the server, then some additional information     // will be sent in the header. See the ParseHeader method     // for details on how this information is extracted.     // If a document is being uploaded, then the ParseHeader method     // will have extracted a value for a "boundary", which is     // the boundary between the different parts of a multipart MIME     // document. The boundary value surrounded by "--" on either side     // indicates the end of the file, so you know that you have     // received everything you need from the request.   If request.query.HasKey("boundary") Then       If InStr(data, "--" + request.query.value("boundary") + "--") > 0 Then           // The end boundary has been found.           // The POST is complete.          Window1.AppendString("POST file Complete...")       Else          // The end boundary has not been found.          // Wait for more data.          Window1.AppendString("Waiting for POST file data...")          Return       End if // There is not "boundary" key in the request.query object, so // check for a value for "Content-Length", which indicates the // number of bytes taken up by the POST data. You can use this // to determine when the POST is complete. Else     Try     // One reason I use Lookahead is to keep all the data together     // so that I can more easily find out the length of the data     // portion of the POST, which I do here by looking at the     // second field of the request, using a blank line as the     // delimiter.     If Len(NthField(data, Endofline.Windows+endofline.Windows, 2)) = Val(Trim(request.Value("Content-Length"))) Then             // Post is complete             Window1.AppendString("POST form Complete...")             // If the POST contains Form data, rather than a document             // being uploaded, you need to parse the data into             // the individual elements for the request.query object.             KeyVal = Split(data, endofline.windows+endofline.Windows)             request.getQueryString(KeyVal(1))    Else       // Wait for more data       Window1.AppendString("Waiting for POST form data...")             Return          End If          End       End If   End If   // The previous code processed the request, primarily   // focusing on parsing the data as it arrived and   // determining when the request was completed.   // Only when all the data has been received will the   // following lines of code run. This portion of the   // event decides what to write back to the client   // and actually does the writing.   // Check to see if it is a "GET" or "POST" command   If (request.Type = "GET") or (request.Type = "POST") Then      // Get the path to the file we want to serve from      // the request object and get a reference to a      // FolderITem object     f = request.getResponseFileFromPath( request.path )     If f = Nil or f.exists = False Then         // The File does not exist, so return error         // Write the header for the file using the         // standard HTTP error code of 404 to indicate         // the file was not found         Write "HTTP/1.1 " + format( 404, "000" ) + " " + _ "Error" + chr(13) + chr(10) + chr(13) + chr(10)         Me.write( "Error 404.")         Return   End If   // If the file was found, then everything is ok so   // you can send the header back to the client with   // a status code of 200, which means, "OK".   // This is the only header information I send back   // but in real life situations, you would be sending   // much more information in the header than I have here.   Write "HTTP/1.1 " + format( 200, "000" ) + " " + _   "OK" + chr(13) + chr(10) + chr(13) + chr(10)   // Test to see if the URL included a value for "dog"   // such as http://localhost:8080/form.html?dog="lucy".   // There is no practical reason for doing this, other   // than to show you how to access the parsed request.   If request.query.HasKey("dog") Then        Write "<html><title>Query</title><body><h1>You dog's name is " + request.query. Value("dog") + "</h1></body></html>"   // Test to see if the request has a field called "textfield"   // which indicates that it is getting its data from a form.   Elseif request.query.HasKey("textfield") Then      Write "<html><title>Post</title><body><h1>Hello, " + request.query.Value("textfield")  + "</h1></body></html>"    // If none of the previous two tests are true, then    // just treat this as a normal request and write    // the file. You need to have the writing executed    // from within the DataAvailable event, or you    // might lose data. The reason for this is that the    // DataAvailable event is asynchronous. If you include    // this code in an external method, then it is possible    // that a new DataAvailable event may be called    // before you are finished writing the data from the    // previous event. By including this code within the    // DataAvailable event, you can make sure that the next    // DataAvailable event is not triggered until you have    // written all that you want to write.    Else         // Open the file as a BinaryStream.         Stream = f.openAsBinaryFile(False)         // Write the file in 64k segments        // This starts the process of writing         // the file back to the client.         // Look at the SendComplete event to         // see what happens once the first 64k         // segment has been sent.         Write Stream.Read( &hFFFF )     End If End If 

Listing 9.7. Sub HTTPConnection.SendComplete(userAborted as Boolean) Handles Event

If Stream <> nil And Not Stream.EOF Then      // In the DataAvailable event, you start to write      // the data back to the client, but you only      // send it out in chunks. Once the first chunk is      // completed being sent, then this event will be      // triggered. At this point, you test to see      // if there is more data to write, then write it      Write stream.Read( &hFFFF )   Else      // If there is no more data to write, then      // disconnect the socket.   Disconnect     // Close the Stream     If Not (Stream is Nil) Then         Stream.Close    End If End if

Listing 9.8. Sub HTTPConnection.parseHeader(data as String)

Dim headers(-1) as String Dim line as String Dim x, count as Integer Dim KeyVal(-1) as String Dim Req(-1) as String Dim ContentType as String Dim SubTypes(-1) as String headers = Split(data, EndOfLine.Windows) count = Ubound(headers) For x = 0 To count    // The first line of a request is the    // Request-Line, according to the HTTP    // Specification and it contains three    // elements, separated by a space.    If x = 0 Then        req = Split(headers(0), " ")        request.type = Trim(req(0))        request.path = Trim(req(1)) Else    KeyVal = Split(headers(x), ": ")    If UBound(KeyVal) = 1 Then        If request.HasKey(Trim(KeyVal(0))) Then            // skip it         Else           request.Value(Trim(KeyVal(0))) = Trim(KeyVal(1))         End If      End If    End If Next If request.type = "GET" Then Elseif request.type = "POST" Then     If request.HasKey("Content-Type") Then         ContentType = request.Value("Content-Type")             If ContentType = "application/x-www-form-urlencoded" Then                  // It's a form             Else                SubTypes = Split(ContentType, ";")                For Each line in SubTypes                    Window1.AppendString(line)                    If Left(Trim(line), 9) = "boundary=" Then                        request.query.value("boundary") = Trim(NthField(line, "=", 2))             End If           Next        End If     End If End If 

Class HTTPRequest Inherits Dictionary
Listing 9.9. HTTPRequest Properties

Type As String Query As Dictionary Path As String Response As FolderItem Protected DocRoot As FolderItem

Listing 9.10. Sub HTTPRequest.Constructor(aDocRoot as FolderItem)

Query = New Dictionary DocRoot = aDocRoot

URL encoded data can be passed either in the body of a POST request or in the URL of a GET request. Either way, the same encoding is used, and the following method parses it and assigns the key/value pairs to the HTTPRequest object.

Listing 9.11. Sub HTTPRequest.getQueryString(QueryString as String)

Dim field, aKey, aValue As String Dim x As Integer query = New Dictionary If QueryString <> "" Then     // Now run through the query string and parse the names and values     // into a dictionary. This code is the same as that used in the     // CGI example. A typical query string looks like this:     // textfield=Mark&Submit=Submit     // All "+" represent spaces, so they need to be replaced.     For x = 1 to CountFields(QueryString, "&")         field = NthField(QueryString, "&", x)         aKey = NthField(field, "=", 1)         aValue = NthField(field, "=", 2)         aValue = ReplaceAll(aValue, "+", " ")         // Some characters are encoded in their HEX values         // preceded by "%", like %20 for a space,         // when sent in a URL. The global DecodeURLComponent         // method will convert them back into their         // character representations.        aValue = DecodeURLComponent(aValue)        query.value(aKey) = aValue    Next End If 

The path to the requested file is sent as part of the first line of the header. The following function calculated the total path relative to the DocRoot FolderItem. If the file requested is a directory, the code looks for a default filename, such as index.html, and returns a reference to that FolderItem.

Listing 9.12. Function HTTPRequest.getResponseFileFromPath(aPath as String) as FolderItem

Dim f as FolderItem Dim url(-1) as String Dim components(-1) as String Dim component as String f = DocRoot // Split path at "?" - the data following the question mark // is the query string url = Split(DecodeURLComponent(path), "?") If Ubound(url) = 1 Then     getQueryString(url(1)) End If If UBound(url) > -1 Then     components = Split(url(0), "/")     For Each component in components         f = f.Child(component)         If f = Nil or f.exists = False Then             Return Nil         End if    Next If f.Directory Then     If f.Child("index.html").Exists Then          f = f.Child("index.html")     ElseIf f.Child("rss.xml").Exists Then          f = f.Child("rss.xml")     ElseIf f.Child("atom.xml").Exists Then         f = f.Child("atom.xml")     End If   End If End If response = f Return response

SSLSocket

The Secure Sockets Layer (SSL) and Transport Layer Security (TLS) are protocols for transmitting encrypted data over the Internet. SSL versions range from 1.0 to 3.0, and TLS is considered the successor to SSL, and is currently at version 1.0 (version 1.1 may be out by the time you read this). The SSLSocket class is the secure version of the TCPSocket class and with a few exceptions, they both work the same way.

There is one important caveat: SSLSocket cannot be used to listen for requests securely; it can only make connections to other secure sockets (like that of a secure web server).

There are a few properties you need to set (or read) when working with SSLSocket. To specify what type of secure connection to use, use the following property:

SSLSocket.ConnectionType as Integer


0

SSL 2.0

1

SSL 3.0 if available, 2.0 otherwise (this is the default)

2

SSL 3.0

3

TSL 1.0


You also do not have to use the SSLSocket class exclusively for secure sockets. Set the following value to true if you want to establish a secure connection alse if you do not want to establish a secure connection:

SSLSocket.Secure as Boolean


The following read-only properties will tell you if you are currently connected to another socket, or if you are in the process of connecting, respectively.

SSLSocket.SSLConnected as Boolean(Read Only) SSLSocket.SSLConnecting as Boolean (Read Only)





REALbasic Cross-Platform Application Development
REALbasic Cross-Platform Application Development
ISBN: 0672328135
EAN: 2147483647
Year: 2004
Pages: 149

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net