macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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

SimpleHTTPConnection

In general, we have to assume that several clients will have open connections to our server at the same time. Each client might issue several new requests while our server is busy fulfilling old ones. We solve this problem by creating a new SimpleHTTPConnection object each time a new client connects to our service. Each SimpleHTTPConnection object will then take care of receiving incoming requests from the specific client connection it manages. In this way requests can always be easily associated with their connections.



Just like when initializing SimpleHTTPServer objects, we also register for appropriate notifications here. But this time we ask for NSFileHandleReadCompletionNotification notifications instead, and we also specify the file handle of the connecting client to make sure that we only receive notifications pertaining to the file handle we actually manage:

@interface SimpleHTTPConnection : NSObject {
    NSFileHandle *fileHandle;
    id delegate;

    CFHTTPMessageRef message;
    BOOL isMessageComplete;
}
- (id)initWithFileHandle:(NSFileHandle *)fh delegate:(id)dl
{
   if( self = [super init] ) {
      fileHandle = [fh retain];
      delegate = [dl retain];
      message = NULL;
      isMessageComplete = YES;

      NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
      [nc addObserver:self
             selector:@selector(dataReceivedNotification:)
                 name:NSFileHandleReadCompletionNotification
               object:fileHandle];
      [fileHandle readInBackgroundAndNotify];
   }
   return self;
}
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    if( message ) CFRelease(message);
    [delegate release];
    [fileHandle release];
    [super dealloc];
}

In the code above we have already prepared two instance variables message and isMessageComplete. These will be used shortly to make sure that we always receive complete HTTP messages.

Working with HTTP Requests

Through NSFileHandle we will only receive raw data. But since we want to talk to web browsers or other clients that use the HTTP protocol, we need to be able to both interpret and create HTTP messages.

Unfortunately, Cocoa has no API for working directly with the HTTP protocol. But it turns out that Core Foundation does. While Core Foundation uses the C language instead of Objective-C, its design is still object-oriented in nature. Indeed many Cocoa classes are wrapping functionality that is already implemented on the Core Foundation level.

Many Core Foundation objects can be used where Cocoa objects are expected and vice versa by simply casting the corresponding pointer. In Apple's documentation this is referred to as "toll-free bridging." Examples we will be using below are the NSURL and NSData classes. For example, pointers to NSURL objects can be used wherever a CFURLRef is expected. But toll-free bridging is not available for every Cocoa class. Developers should always consult the documentation first.

To interpret incoming data as HTTP messages, we will be using the following Core Foundation functions: CFHTTPMessageCreateEmpty, CFHTTPMessageAppendBytes, CFHTTPMessageIsHeaderComplete, and CFHTTPMessageCopyRequestURL. In general, there is no way to guarantee that a complete HTTP message will be received in one single piece. To deal with this, we will first create a new empty HTTP message:

message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);

and then append received data to it as long as CFHTTPMessageIsHeaderComplete returns FALSE.

The CFHTTPMessage object message is created in the process' memory. We own Core Foundation objects that are created with functions containing "Create" or "Copy" in their names. Such objects need to be released using CFRelease.

HTTP messages come in two flavors: HTTP requests and HTTP responses. HTTP requests are sent to a server, which in turn replies with a HTTP response. By passing TRUE for the second argument of CFHTTPMessageCreateEmpty we specify that we are dealing with a request.

Once a HTTP request has been received completely, we can extract the request-URL using CFHTTPMessageCopyRequestURL(message). The details are as follows:

- (void)dataReceivedNotification:(NSNotification *)notification
{
   NSData *data = [[notification userInfo] objectForKey:
                                     NSFileHandleNotificationDataItem];
    
   if ( [data length] == 0 ) {
      // NSFileHandle's way of telling us
      // that the client closed the connection
      [delegate closeConnection:self];
   } else {
      [fileHandle readInBackgroundAndNotify];
        
      if( isMessageComplete ) {
            message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);
      }
      Boolean success = CFHTTPMessageAppendBytes(message, [data bytes],
                                                 [data length]);
      if( success ) {
         if( CFHTTPMessageIsHeaderComplete(message) ) {
            isMessageComplete = YES;
            CFURLRef url = CFHTTPMessageCopyRequestURL(message);
            [delegate newRequestWithURL:(NSURL *)url connection:self];
            CFRelease(url);
            CFRelease(message);
            message = NULL;
         } else {
            isMessageComplete = NO;
         }
      } else {
         NSLog(@"Incomming message not a HTTP header, ignored.");
         [delegate closeConnection:self];
      }
   }
}

This time NSNotification's user info dictionary contains an NSData object that encapsulates the received bytes.

Note that delegate (of class SimpleHTTPServer) is assumed to respond to the methods newRequestWithURL:connection: and closeConnection:. The implementation of these methods is a routine task. SimpleHTTPServer collects all requests in an NSMutableArray. Every entry is a NSDictionary that records the URL, connection, and date of the request. Whenever a client aborts a connection, SimpleHTTPServer makes sure that every pending request pertaining to this connection is removed. The reader is referred to the source code for more details.

Let's step back for a moment and take look at the whole picture:

Figure 1

Pages: 1, 2, 3, 4

Next Pagearrow