Reader beware: this post is part of the older "Objective-C era" on Cocoa with Love. I don't keep these articles up-to-date and many contain code that no longer works or is superceded by newer APIs. Many others contain out-of-date information or offer advice and opinions I no longer endorse. Read "A new era for Cocoa with Love" for more.
HTTP is one of the simpler protocols to implement for communication between computers. On the iPhone, since there are no APIs for data synchronization or file sharing, embedding an HTTP server is one of the best ways to transfer data from your iPhone application to a computer. In this post I'll show you how to write your own simple but extensible HTTP server. The server classes will also work on Mac OS X (Cocoa un-Touched).
In this post I will present the following sample application:
The application is very simple: you can edit text and save it to a file (it is always saved to the same file).
While the application is running, it also runs an HTTP server on port 8080. If the path "/" is requested, it will return the content of the saved file. All other requests return a 501 error.
To transfer the text file from the iPhone application, just enter the IP address of the phone followed by ":8080" in any web browser.
HTTPServer and HTTPResponseHandler classes
The approach I use for an HTTP server involves two classes: the server (which listens for connections and reads data up to the end of the HTTP header) and the response handler (which sends the response and may choose to read from the connection past the header).
The key design choice for me was simplicity of each new response implementation: the server and response classes are designed so that a new response implementation need only implement three methods:
canHandleRequest:method:url:headerFields:— to decide if the implementation can handle a specific request
startResponse— to begin writing (or completely write) the response
load— all subclasses should implement the standard
+[NSObject load]method to register themselves with the base class
It is just a tiny HTTP server but the approach should allow you to quickly integrate HTTP communications into any application.
Opening a socket for listening
Most server communications, HTTP included, begin by creating a socket for listening.
Sockets in Cocoa can be created and configured entirely using the BSD sockets code but it's often marginally easier to use the CoreFoundation
CFSocket API where possible. Unfortunately, that only makes it marginally easier — we still have a large block of boilerplate code to throw down just to open a socket.
-[HTTPServer start] method:
This is a large block of code but it's really only doing one thing: opening a socket to listen for TCP connections on the port specified by
HTTP_SERVER_PORT (which is 8080 for this application).
There is some additional work because I like to specify
SO_REUSEADDR. This lets us reclaim the port if it is open but idle (a common occurrence if we restart the program immediately after a crash or killing the application).
Receiving incoming connections
After the socket is setup, Cocoa handles a little more of the work so things get easier.
We can receive each incoming connection by constructing an
NSFileHandle from the
fileDescriptor above and listening for connection notifications
-[HTTPServer start:] method (immediately below the previous code):
receiveIncomingConnectionNotification: is invoked, each new incoming connection will get its own
NSFileHandle. If you're keeping track, that was:
- 1 file handle (
listeningHandle) manually created from the socket
fileDesriptorto listen on the socket for new connections.
- 1 file handle automatically created for each new connection received through
listeningHandle. We'll continue to listen to these new handles (the keys in the
incomingRequestsdictionary) to record the data for each connection.
So, now that we've received a new, automatically created file handle, we create a
CFHTTPMessageRef (which will store the incoming data) we receive over the file handle. We store these as the objects in
incomingRequests dictionary to allow easy access to the
CFHTTPMessageRef for each file handle
CFHTTPMessageRef is both storage and the parser for the incoming data. We can invoke
CFHTTPMessageIsHeaderComplete() every time we add data to check when the HTTP headers are complete and we can spawn a response handler.
The response handler is spawned in the
-[HTTPServer receiveIncomingDataNotification:] method:
The server stops listening to the file handle for the connection at this point but it doesn't close it, since the file handle is passed to the
HTTPResponseHandler so that the HTTP response can be sent back over the same file handle.
Flexible response handling
Exactly which subclass the
+[HTTPResponseHandler handlerForRequest:fileHandle:server:] method chooses to return determines the entire content of the response. It does this by iterating over a priority ordered array of the registered handlers and asking each one if it wants to handle the request.
For this to work, all
HTTPResponseHandlers need to be registered with the base class. The easiest way to do this is to add an implementation of the
+[NSObject load] method to every subclass:
In the sample application, the only response handler other than the default is the
AppTextFileResponse. This class chooses to handle the response when the
requestURL is equal to "/".
AppTextFileResponse then handles the entire response synchronously (before returning from the
startResponse method) by writing the text file saved by the application as the response body.
[server closeHandler:self]; invocation tells the server to remove this
HTTPResponseHandler from the set of active handlers. The server will invoke
endReponse when it removes this handler (which is where we close the connection — since this handler does not support
Work not implemented
The biggest task not handled in this implementation is parsing the HTTP request body.
The reason for this is that a general HTTP body solution is very complicated. The body's size may be specified by the
Content-Length header but it need not be — so knowing where the body ends can be difficult. The body may also be encoded in one of about a dozen different
gzip — each of which require different processing.
However, I have never needed to implement a generic solution. It is normally easiest to determine what is needed for your specific needs and handle the HTTP body in accordance with those needs. You can handle the request body by overriding
-[HTTPRequestHandler receiveIncomingDataNotification:]. The default implementation ignores all data it receives after the HTTP request headers.
Data handling note: the first time the
-[HTTPRequestHandler receiveIncomingDataNotification:]method is called, the initial bytes of the HTTP body will already have been read from the
fileHandleand appended to the
requestinstance variable. If you need to read the body, either continue reading into the
requestobject, or remember to include this initial data.
Another task not handled are Keep-Alive connections. These also need to be handled in
-[HTTPRequestHandler receiveIncomingDataNotification:] and I've left a big comment on the method about what would be involved. The reality is that it's probably easier to set the
Connection header field to
close for every response to tell the client that you're not going to handle Keep-Alive (see the
startResponse code sample above for an example).
HTTPReseponseHandler priority does not take the requested
Content-Type into account. If this is an important consideration for you, you might want to change how
+[HTTPResponseHandler handlerClassForRequest:method:url:headerFields:] selects its handler.
Finally, this server does not handle SSL/TLS. It is intended for local network transfers where the network itself is relatively secure. If you are transferring over the open internet and want a secure connection, there's a lot to change and manage at the socket level. You could try it yourself but if security is really important, you probably shouldn't risk writing your own server — if you can arrange it, use a mature, TLS-enabled HTTP server and only handle the client-side in your application. Client-side security in Cocoa is very easy — it is automatic and transparent in
Download the sample app TextTransfer.zip (45kB) which includes the
Just because mainstream HTTP servers are large, complex pieces of software, doesn't mean an HTTP server is necessarily large and complex — the core implementation here is only two classes, yet is flexible and configurable.
Of course, the end result is not intended for use as a complete web server solution, however it should work well as a small communications portal into your custom iPhone or Mac applications.
HashValue: an object for holding MD5 and SHA hashes
Temporary files and folders in Cocoa