macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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

SimpleHTTPServer

To wrap up we will create a SimpleHTTPServer class and put everything we have learned so far into an initWithPortNumber:delegate: initializer method as follows:



@interface SimpleHTTPServer : NSObject {
   int portNumber;
   id delegate;

   NSSocketPort *socketPort;
   NSFileHandle *fileHandle;

   NSMutableArray *connections;
   NSMutableArray *requests;    

    ...
}
- (id)initWithPortNumber:(int)pn delegate:(id)dl
{
   if( self = [super init] ) {
      portNumber = pn;
      delegate = [dl retain];

      connections = [[NSMutableArray alloc] init];
      requests = [[NSMutableArray alloc] init];

    ...

      socketPort = [[NSSocketPort alloc] initWithTCPPort:portNumber];
      int fd = [socketPort socket];
      fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd
                                                 closeOnDealloc:YES];

      NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
      [nc addObserver:self
             selector:@selector(newConnection:)
                 name:NSFileHandleConnectionAcceptedNotification
               object:nil];
 
      [fileHandle acceptConnectionInBackgroundAndNotify];
}
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    ...
    [requests release];
    [connections release];
    [fileHandle release];
    [socketPort release];
    [delegate release];
    [super dealloc];
}

The requests and connections instance variables will be used to keep track of open connections and pending requests, while delegate holds a pointer to an object responsible for doing the actual processing necessary to fulfill the request. In our example it will render a web page to PDF.

Once a SimpleHTTPServer object is created it will immediately begin listening to the specified port for incoming connections. As soon as a client begins to talk to our computer on the right port, NSFileHandle will issue an NSFileHandleConnectionAcceptedNotification which causes our newConnection: method to be called, since this is what we specified when we registered with NSNotificationCenter.

The implementation of the newConnection: method is as follows. Note that we are already using a soon to be declared SimpleHTTPConnection class. While SimpleHTTPServer has the responsibility to accept newly connecting clients, all communication from a specific client will be channeled through a corresponding SimpleHTTPConnection object:

- (void)newConnection:(NSNotification *)notification
{
   NSDictionary *userInfo = [notification userInfo];
   NSFileHandle *remoteFileHandle = [userInfo objectForKey:
                           NSFileHandleNotificationFileHandleItem];

   NSNumber *errorNo = [userInfo objectForKey:@"NSFileHandleError"];
   if( errorNo ) {
      NSLog(@"NSFileHandle Error: %@", errorNo);
      return;
   }

   [fileHandle acceptConnectionInBackgroundAndNotify];

   if( remoteFileHandle ) {
      SimpleHTTPConnection *connection;
      connection = [[SimpleHTTPConnection alloc] initWithFileHandle:
                                                        remoteFileHandle
                                                           delegate:self];
      if( connection ) {
         NSIndexSet *insertedIndexes;
         insertedIndexes = [NSIndexSet indexSetWithIndex:
                                                   [connections count]];
         [self willChange:NSKeyValueChangeInsertion
             valuesAtIndexes:insertedIndexes forKey:@"connections"];
         [connections addObject:connection];
         [self didChange:NSKeyValueChangeInsertion
             valuesAtIndexes:insertedIndexes forKey:@"connections"];
         [connection release];
      }
   }
}

The most notable thing here is the fact that through NSNotification's user info dictionary we are passed another instance of NSFileHandle. This new file handle represents the specific client who has connected to our service. We are using it to initialize a new SimpleHTTPConnection object. It is important not to forget to call acceptConnectionInBackgroundAndNotify on fileHandle again, since NSFileHandle will otherwise stop listening for incoming connections.

As a bonus, the code presented here makes sure it plays nicely with Cocoa bindings. It is possible to bind the connections instance variable to an NSArrayController, which in turn can easily be hooked up to a user interface widget, all from within Interface Builder. Bracketing our changes of the connections array with explicit calls to the observer notification methods willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: makes sure that the bindings layer will take notice.

Usually, such notifications are created automatically when instance variables are set. Here, however, we are not setting the connections instance variable itself, but we are mutating the referenced array. This will go unnoticed by the Cocoa bindings layer as long as we are not either using so called indexed accessor methods or are creating observer notifications manually. We have chosen to do the latter. Care has to be taken though, to not mix automatic and manual notification. Our newConnection: method will never participate in a binding. If it were, we would have to disable automatic observer notification first, for otherwise unexpected behavior and even crashes could result. More details on manual versus automatic observer notification can be found in Apple's Key-Value Observing Programming Guide.

Pages: 1, 2, 3, 4

Next Pagearrow