macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

How to Write a Cocoa Web Server
Pages: 1, 2, 3, 4

AppController

Our example Xcode project contains an AppController class that manages the complete application infrastructure. On application launch it creates a single SimpleHTTPServer object:



server = [[SimpleHTTPServer alloc] initWithTCPPort:50000
                                          delegate:self];

Note that AppController passes itself as the delegate. While the SimpleHTTPServer and SimpleHTTPConnection classes insulate AppController from networking details, the actual processing of requests is done by AppController itself. SimpleHTTPServer sends the following methods to its delegate to coordinate the processing of requests:

- (void)processURL:(NSURL *)path
        connection:(SimpleHTTPConnection *)connection;
- (void)stopProcessing;

Once AppController is finished processing a URL it replies to server with either of the following methods, depending on whether the URL could be processed successfully or not:

- (void)replyWithData:(NSData *)data MIMEType:(NSString *)type;
- (void)replyWithStatusCode:(int)code message:(NSString *)message;

SimpleHTTPServer makes sure that only one request is sent to its delegate for processing at any time. If a request that has already been forwarded to AppController is aborted by the client, SimpleHTTPServer sends the stopProcessing method to notify its delegate accordingly. SimpleHTTPServer will not send further requests for processing until either the request has been aborted or one of the replyWith... methods has been called.

Sending HTTP Responses

SimpleHTTPServer handles the replyWith... methods by using the following more general method:

- (void)replyWithStatusCode:(int)code
                    headers:(NSDictionary *)headers
                       body:(NSData *)body;

The method signature already reflects the fact that every HTTP response consists of a status line (containing a status code and a reason phrase), optional header fields, and the bulk of the message contained in the body. The most well-known status code probably is 404 (reason phrase "Not Found"), which signifies that the server was not able to find anything matching the request-URL. We use 200 for successfully completed requests. A full list of codes can be found in RFC2616's Status Code Section.

Core Foundation has a number of functions that make it easy to construct a HTTP response. We begin with creating a CFHTTPMessage object:

CFHTTPMessageRef msg;
msg = CFHTTPMessageCreateResponse(kCFAllocatorDefault,
                                  code,  // status code
                                  NULL,  // use standard reason phrase
                                  kCFHTTPVersion1_1);

Then we add header fields to it. Header fields contain meta data about the bytes being sent. Every header field consists of a field name and a field value. For example, to add a header field that specifies the MIME type of the message body we use:

CFHTTPMessageSetHeaderFieldValue(msg,
                                 CFSTR("Content-Type"),
                                 CFSTR("application/pdf"));

The CFSTR() function constructs a CFString object from a C string. CFString is toll-free bridged with NSString. The following function call adds AppController's data to the response:

CFHTTPMessageSetBody(msg, (CFDataRef)body);

Once we have finished constructing the HTTP response, we need to serialize it for transmission over the network:

CFDataRef msgData = CFHTTPMessageCopySerializedMessage(msg);

The raw data we have thus obtained can be sent to the client by using the writeData: method on the client's connection file handle. We have to exercise some care here, since it is possible that the network connection fails while transmitting our serialized HTTP response, in which case NSFileHandle raises an exception. It suffices to enclose the method call within a @try block:

@try {
   [remoteFileHandle writeData:(NSData *)msgData];
}
@catch (NSException *exception) {
   NSLog(@"Error while transmitting data");
}

Replacing NSSocketPort with Unix Calls

When testing code that uses BSD sockets there is one common problem. When the tested app crashes or is forced to quit for whatever reason, the socket still exists in the system kernel, and might stay there for a couple of minutes until a timeout kicks in. Rerunning the app before the socket has been freed results in an error. Using the following Unix call we can tell a socket to reuse the given address, thus effectively preventing this error:

int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));

Here fd is the file descriptor of the socket we are interested in. setsockopt() is a general interface for setting socket parameters. The problem here is that we need to call setsockopt() before fd is bound to an address/port combination. Thus we cannot use it after creating a NSSocketPort. Instead we have to completely replace NSSocketPort with direct Core Foundation and Unix calls as follows:

int fd = -1;
CFSocketRef socket;
socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,
                        IPPROTO_TCP, 0, NULL, NULL);
if( socket ) {
   fd = CFSocketGetNative(socket);
   int yes = 1;
   setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
      
   struct sockaddr_in addr;
   memset(&addr, 0, sizeof(addr));
   addr.sin_len = sizeof(addr);
   addr.sin_family = AF_INET;
   addr.sin_port = htons(port);
   addr.sin_addr.s_addr = htonl(INADDR_ANY);
   NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
   if( CFSocketSetAddress(socket, (CFDataRef)address) !=
          kCFSocketSuccess ) {
      NSLog(@"Could not bind to address");
   }
} else {
   NSLog(@"No server socket");
}

After this piece of code executes successfully, fd will contain the valid file descriptor of our server socket.

We have used three Core Foundation functions here, CFSocketCreate, CFSocketGetNative, and CFSocketSetAddress. But the constants we used and the sockaddr_in structure are taken from Unix. We won't go into the details here, but instead refer to an article by Mike Beam, who did an excellent job explaining BSD sockets.

Conclusion

We have seen how to use NSSocketPort, NSFileHandle, and Cocoa notifications to write a server application without having to create a multi-threaded application. We have seen how Core Foundation relieves us of directly working with the low level details of the HTTP protocol. Finally, we have written some code which helps to take away the frustration of having to wait for sockets to be freed by the system kernel. A complete working example, that also demonstrates how to use WebKit for rendering a web page to PDF, can be found at culturedcode.com/cocoa.

Jürgen Schweizer is president of Cultured Code, a small software company he founded in 2004.


Return to the Mac DevCenter.