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.
Showing messages 1 through 9 of 9.
-
Server class
2008-05-29 07:53:50 david.koster [Reply | View]
Whoever is interested in writing a server or a client application but doesn't want to fiddle around with all the sockets and stuff, I have implemented a basic server class, that may be used, modified or subclassed to do anything your application needs!
http://www.david-koster.de/code/simpleserver/
btw: This article was a great help! Thanks a lot!
-
Not quite finished yet
2006-12-30 15:01:34 olmeca [Reply | View]
While experimenting with this web server implementation I discovered that it does not support processing the message body of the request. With other words it cannot be used for HTTP POST requests, and - as a consequence - not for processing web services.
-
Not quite finished yet
2006-12-31 02:34:44 JürgenSchweizer [Reply | View]
Thanks for giving me the opportunity to clarify this point. The sample Xcode project I provided with this article is by no means intended to be a fully featured HTTP server library. It is in fact only a minimal implementation that illustrates how to get started using Cocoa and Core Foundation to process HTTP Requests. I hope the code is simple enough for interested readers to be able to rewrite or extend it.
-
Why not NSStreams ?
2006-12-03 11:54:36 sourbox [Reply | View]
Apple has a CocoaHTTPServer sample, located at http://developer.apple.com/samplecode/CocoaHTTPServer/index.html .
Any advantage using your method instead of the Apple way ? -
Why not NSStreams ?
2006-12-04 00:52:59 JürgenSchweizer [Reply | View]
Apple's sample code is actually much more low level than ours. It uses the call back mechanism of the Core Foundation function CFSocketCreate to accept new connections. Only later is the call back converted to a method invocation and a pair of NSStreams is created from a native BSD socket using CFStreamCreatePairWithSocket. While the use of CFSocket and CFStream is more flexible, our emphasis has been on simplicity. In fact, I believe that the code discussed in this article shows the simplest way to implement a basic HTTP server on Mac OS X.
-
But why?
2006-11-15 13:45:24 StefanTilkov [Reply | View]
Granted, this surely was a fascinating project -- but what was the reason given that OS X ships with a built-in, high-performance, modular, extensible, debugged, stable and free HTTP server already? -
But why?
2006-11-16 01:42:04 JürgenSchweizer [Reply | View]
Stefan, you might find SimpleHTTPServer (or Ruder's Netclasses for that matter) useful in situations where you need an embedded but simple HTTP server right within your application. It is sometimes interesting to use a server/client architecture without the involvement or even the knowledge of the user. In such a case you neither want to bother the user with setting up Mac OS X's built-in Apache server or modify a maybe already existing Apache configuration. A common example would be a peer to peer file sharing application.
In our case, the software had to run on an unmodified Mac mini and integrate into an existing cross platform network that already used the HTTP protocol.
-
netclasses
2006-11-15 06:34:30 leeg [Reply | View]
I wrote a trivial server on GNUstep-base using aeruder's netclasses: http://netclasses.aeruder.net which handles setting up the socket and asynchronous communications. You basically register a controller object with the[NetApplication sharedInstance], which then gets notified of things like connection attempts, data received, connection lost etc. My server came in at 98 lines ;-)






Can anyone post a bugfix for this problem?
James