How to Write a Cocoa Web Serverby Jürgen Schweizer
Editor's Note: Jürgen Schweizer is a new contributor to Mac DevCenter. Jürgen is president of Cultured Code, a small software company he founded in 2004. Its first product is the award-winning web design tool Xyle scope.
A couple of weeks ago a friend asked me to write a Mac-based HTTP server for an intranet web service he was building. I hadn't forgotten how Mac OS X was praised because of its Unix underpinnings, and the strong networking capabilities that come along with that. As a Cocoa developer, I was confident that I could find a nice Cocoa API that would do the hard work for me, so I accepted...
There are many powerful Cocoa APIs indeed, but none of which seemed to be exactly addressing the problem of writing a small homegrown web server. For example, there is the Distributed Objects (DO) framework that allows objects to send messages to each other through the network. Although extremely convenient, it is limited to communication between Objective-C objects, and cannot be used to build a more general server. In fact, it turned out that it was not possible to realize the HTTP server I sought without using some Unix and Core Foundation APIs as well.
However, there are some rather useful, although somewhat non-obvious, Cocoa classes that are most helpful for writing a web server. In this article I will show you what they are and how to use them. I will also show you when it is necessary to (or when some additional benefits can be gained) use Unix or Core Foundation APIs. This article assumes a basic knowledge of Cocoa, so I won't explain any Cocoa topics here, but will show everything you need to get started with networking. To have a concrete example at our hands, we will use the general classes we are building to create a server that accepts a URL and instead returns a PDF rendering of the web page at the given address. The full source code of the Xcode project is available at culturedcode.com/cocoa.
NSFileHandle and Network Communication
One of the problems in writing server software is the need to make sure that the server is always able to accept new requests no matter how busy it is fulfilling the old ones. Usually this requires the use of multiple threads. But fortunately, Cocoa provides us with a class that relieves us of creating a multi-threaded application:
NSFileHandle. Yes, that's right. Despite the word "file" in the class' name, and the fact that it is not listed under Networking in Apple's documentation, it is just the right choice for handling all of our network communication needs. The explanation for this odd naming lies in Mac OS X's Unix foundation.
In Unix, pretty much everything is treated as a file. Physical devices, network connections, and yes, also ordinary files on your hard disk are all accessed using a common abstraction: file descriptors. A file descriptor is simply an integer that provides an index into a per-process table that references resources available to your process. Once we have the file descriptor
fd of an already available resource, we can use it to create a new
NSFileHandle object, like in the following code fragment:
NSFileHandle *fileHandle; fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
NSFileHandle does not create network resources for us, but manages existing ones. For example, it will listen in the background for new connections or incoming data, notifying its delegate when appropriate. This capability of
NSFileHandle is relieving us of spawning a new thread to do that work. Passing in
closeOnDealloc: makes sure that the resource managed by
fileHandle is properly closed when we are finished with it.
But how do we get an appropriate network resource and corresponding file descriptor? The Unix abstraction for network resources is called BSD Sockets, and we will see shortly how to get our hands on one.
But first we need to choose on which port to make our service available to the outside world. Remember that a computer in a network is typically available at a specific IP address, which is commonly specified using four numbers (in the range from 0 to 255) separated by dots, e.g., 127.0.0.1. However, a single computer may provide many different services. To make this possible a single IP address is further divided into 65,536 different ports. All incoming messages specify a destination port. An application (or rather a process) can bind to specific ports, which means that incoming communications on those ports will be forwarded to the bound process.
For experimentation it is advisable to choose one of the unregistered ports (49152-65535). Ports can be registered with the Internet Assigned Numbers Authority (IANA).
The Easy Way to Sockets
To receive communications, we need to create an entry point in our process where data can flow in, and bind it to the IP address of our computer and the port number we have chosen. Such entry points are known as BSD sockets, or simply sockets, and are identified via file descriptors.
Again, Cocoa has a class that handles the necessary details for us. This class is known by the not so surprising name of
NSSocketPort. This time, however, the gotcha is that its documentation is buried deeply within the Distributed Objects API, so much so that some developers even doubt that it can be used reliably for purposes other than that of serving the DO architecture. Rest assured that
NSSocketPort will do a nice job for us. For all those still in doubt, the end of this article shows how to replace
NSSocketPort with direct Unix calls and why one might want to do so.
To obtain our sought for file descriptor
fd we use:
NSSocketPort *socketPort; socketPort = [[NSSocketPort alloc initWithTCPPort:PORT_NUMBER]; int fd = [socketPort socket];
PORT_NUMBER is the port number of our service (0...65535). It is important not to release
socketPort before being finished with
Now that we have established a connection to the outside world, it would be nice if we could actually start accepting incoming connections. And we are only one method call away from reaching this goal:
This method call immediately returns, but
fileHandle will keep listening to the socket until the first connection comes in. Then it issues an
NSFileHandleConnectionAcceptedNotification, which carries further information about the incoming connection.