# HG changeset patch # User Jens Alfke # Date 1211675106 25200 # Node ID 9fdd8dba529c08bd07515cc226d56b3b2b164c05 # Parent 8267d5c429c4dc48c6cef708bfb7d2e1d715262f * Added more documentation. * Minor API changes. diff -r 8267d5c429c4 -r 9fdd8dba529c .hgignore --- a/.hgignore Sat May 24 13:26:02 2008 -0700 +++ b/.hgignore Sat May 24 17:25:06 2008 -0700 @@ -1,6 +1,9 @@ syntax: glob .DS_Store build +Documentation +Doxyfile +uploadDocs .svn (*) *.pbxuser diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPConnection.h --- a/BLIP/BLIPConnection.h Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPConnection.h Sat May 24 17:25:06 2008 -0700 @@ -25,6 +25,13 @@ /** The delegate object that will be called when the connection opens, closes or receives messages. */ @property (assign) id delegate; +/** The connection's request dispatcher. By default it's not configured to do anything; but you + can add rules to the dispatcher to call specific target methods based on properties of the + incoming requests. + + Requests that aren't handled by the dispatcher (i.e. all of them, by default) will be + passed to the delegate's connection:receivedRequest: method; or if there's no delegate, + a generic error response will be returned. */ @property (readonly) BLIPDispatcher *dispatcher; /** Creates an outgoing request, with no properties. @@ -63,7 +70,8 @@ @optional /** Called when a BLIPResponse (to one of your requests) is received from the peer. - This is called after the response object's onComplete target, if any, is invoked. */ + This is called after the response object's onComplete target, if any, is invoked. + (This method is optional.) */ - (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response; @end @@ -78,6 +86,10 @@ BLIPDispatcher *_dispatcher; } +/** The default request dispatcher that will be inherited by all BLIPConnections opened by this + listener. + If a connection's own dispatcher doesn't have a rule to match a message, this inherited + dispatcher will be checked next. Only if it fails too will the delegate be called. */ @property (readonly) BLIPDispatcher *dispatcher; @end diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPConnection.m --- a/BLIP/BLIPConnection.m Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPConnection.m Sat May 24 17:25:06 2008 -0700 @@ -66,7 +66,7 @@ [self tellDelegate: @selector(connection:receivedRequest:) withObject: request]; if( ! request.noReply && ! request.repliedTo ) { LogTo(BLIP,@"Returning default empty response to %@",request); - [request respondWithData: nil]; + [request respondWithData: nil contentType: nil]; } }@catch( NSException *x ) { MYReportException(x,@"Dispatching BLIP request"); diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPDispatcher.h --- a/BLIP/BLIPDispatcher.h Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPDispatcher.h Sat May 24 17:25:06 2008 -0700 @@ -10,19 +10,48 @@ @class MYTarget, BLIPMessage; +/** Routes BLIP messages to targets based on a series of rule predicates. + Every BLIPConnection has a BLIPDispatcher, which is initially empty but you can add rules + to it. + + Every BLIPListener also has a dispatcher, which is inherited as the parent by every + connection that it accepts, so you can add rules to the listener's dispatcher to share them + between all connections. + + It's not necessary to use a dispatcher. Any undispatched requests will be sent to the + BLIPConnection's delegate's -connection:receivedRequest: method, which can do its own + custom handling. But it's often easier to use the dispatcher to associate handlers with + request based on property values. */ @interface BLIPDispatcher : NSObject { NSMutableArray *_predicates, *_targets; BLIPDispatcher *_parent; } +/** The inherited parent dispatcher. + If a message does not match any of this dispatcher's rules, it will next be passed to + the parent, if there is one. */ @property (retain) BLIPDispatcher *parent; +/** Adds a new rule, to call a given target method if a given predicate matches the message. */ - (void) addTarget: (MYTarget*)target forPredicate: (NSPredicate*)predicate; + +/** Convenience method that adds a rule that compares a property against a string. */ +- (void) addTarget: (MYTarget*)target forValueOfProperty: (NSString*)value forKey: (NSString*)key; + +/** Removes all rules with the given target method. */ - (void) removeTarget: (MYTarget*)target; -- (void) addTarget: (MYTarget*)target forValueOfProperty: (NSString*)value forKey: (NSString*)key; - +/** Tests the message against all the rules, in the order they were added, and calls the + target of the first matching rule. + If no rule matches, the message is passed to the parent dispatcher's -dispatchMessage:, + if there is a parent. + If no rules at all match, NO is returned. */ - (BOOL) dispatchMessage: (BLIPMessage*)message; +/** Returns a target object that will call this dispatcher's -dispatchMessage: method. + This can be used to make this dispatcher the target of another dispatcher's rule, + stringing them together hierarchically. */ +- (MYTarget*) asTarget; + @end diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPDispatcher.m --- a/BLIP/BLIPDispatcher.m Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPDispatcher.m Sat May 24 17:25:06 2008 -0700 @@ -84,6 +84,12 @@ } +- (MYTarget*) asTarget; +{ + return $target(self,dispatchMessage:); +} + + @end diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPMessage.h --- a/BLIP/BLIPMessage.h Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPMessage.h Sat May 24 17:25:06 2008 -0700 @@ -31,7 +31,7 @@ }; -/** Abstract superclass for both requests and responses. */ +/** Abstract superclass for BLIP requests and responses. */ @interface BLIPMessage : NSObject { BLIPConnection *_connection; @@ -65,12 +65,12 @@ /** Has the entire message, including the body, arrived? */ @property (readonly) BOOL complete; -/** Should the message body be compressed using gzip? - This property can only be set before sending the message. */ +/** Should the message body be compressed with gzip? + This property can only be set before sending the message. */ @property BOOL compressed; /** Should the message be sent ahead of normal-priority messages? - This property can only be set before sending the message. */ + This property can only be set before sending the message. */ @property BOOL urgent; /** Can this message be changed? (Only true for outgoing messages, before you send them.) */ @@ -84,7 +84,8 @@ #pragma mark PROPERTIES: -/** The message's properties, a dictionary-like object. */ +/** The message's properties, a dictionary-like object. + Message properties are much like the headers in HTTP, MIME and RFC822. */ @property (readonly) BLIPProperties* properties; /** Mutable version of the message's properties; only available if this mesage is mutable. */ diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPProperties.m --- a/BLIP/BLIPProperties.m Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPProperties.m Sat May 24 17:25:06 2008 -0700 @@ -20,7 +20,7 @@ "Error-Code" "Error-Domain", "application/octet-stream", - "text/plain", + "text/plain; charset=UTF-8", "text/xml", "text/yaml", "application/x-cloudy-signed+yaml", diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPReader.m --- a/BLIP/BLIPReader.m Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPReader.m Sat May 24 17:25:06 2008 -0700 @@ -102,11 +102,9 @@ SInt32 headerLeft = sizeof(BLIPFrameHeader) - _curBytesRead; if( headerLeft > 0 ) { // Read (more of) the header: - NSInteger bytesRead = [(NSInputStream*)_stream read: (uint8_t*)&_curHeader +_curBytesRead - maxLength: headerLeft]; - if( bytesRead < 0 ) { - [self _gotError]; - } else { + NSInteger bytesRead = [self read: (uint8_t*)&_curHeader +_curBytesRead + maxLength: headerLeft]; + if( bytesRead > 0 ) { _curBytesRead += bytesRead; if( _curBytesRead < sizeof(BLIPFrameHeader) ) { // Incomplete header: @@ -135,10 +133,8 @@ if( bodyRemaining > 0 ) { uint8_t *dst = _curBody.mutableBytes; dst += _curBody.length - bodyRemaining; - NSInteger bytesRead = [(NSInputStream*)_stream read: dst maxLength: bodyRemaining]; - if( bytesRead < 0 ) - return (void)[self _gotError]; - else if( bytesRead > 0 ) { + NSInteger bytesRead = [self read: dst maxLength: bodyRemaining]; + if( bytesRead > 0 ) { _curBytesRead += bytesRead; bodyRemaining -= bytesRead; LogTo(BLIPVerbose,@"%@: Read %u bytes of frame body (%u left)",self,bytesRead,bodyRemaining); diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPRequest.h --- a/BLIP/BLIPRequest.h Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPRequest.h Sat May 24 17:25:06 2008 -0700 @@ -39,8 +39,8 @@ /** Sends this request over its connection. (Actually, the connection queues it to be sent; this method always returns immediately.) - If this request has not been assigned to a connection, an exception will be raised. - Its matching response object will be returned, or nil if the request couldn't be sent. */ + Its matching response object will be returned, or nil if the request couldn't be sent. + If this request has not been assigned to a connection, an exception will be raised. */ - (BLIPResponse*) send; /** Does this request not need a response? @@ -58,8 +58,9 @@ It will prevent a default empty response from being sent upon return from the request handler. */ - (void) deferResponse; -/** Shortcut to respond to this request with the given data. */ -- (void) respondWithData: (NSData*)data; +/** Shortcut to respond to this request with the given data. + The contentType, if not nil, is stored in the "Content-Type" property. */ +- (void) respondWithData: (NSData*)data contentType: (NSString*)contentType; /** Shortcut to respond to this request with the given string (which will be encoded in UTF-8). */ - (void) respondWithString: (NSString*)string; diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPRequest.m --- a/BLIP/BLIPRequest.m Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPRequest.m Sat May 24 17:25:06 2008 -0700 @@ -103,9 +103,25 @@ return _response != nil; } -- (void) respondWithData: (NSData*)data {self.response.body = data; [self.response send];} -- (void) respondWithString: (NSString*)string {[self respondWithData: [string dataUsingEncoding: NSUTF8StringEncoding]];} -- (void) respondWithError: (NSError*)error {self.response.error = error; [self.response send];} +- (void) respondWithData: (NSData*)data contentType: (NSString*)contentType +{ + BLIPResponse *response = self.response; + response.body = data; + response.contentType = contentType; + [response send]; +} + +- (void) respondWithString: (NSString*)string +{ + [self respondWithData: [string dataUsingEncoding: NSUTF8StringEncoding] + contentType: @"text/plain; charset=UTF-8"]; +} + +- (void) respondWithError: (NSError*)error +{ + self.response.error = error; + [self.response send]; +} - (void) respondWithErrorCode: (int)errorCode message: (NSString*)errorMessage { diff -r 8267d5c429c4 -r 9fdd8dba529c BLIP/BLIPTest.m --- a/BLIP/BLIPTest.m Sat May 24 13:26:02 2008 -0700 +++ b/BLIP/BLIPTest.m Sat May 24 17:25:06 2008 -0700 @@ -162,7 +162,7 @@ - (void) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request { Log(@"***** %@ received %@",connection,request); - [request respondWithData: request.body]; + [request respondWithData: request.body contentType: request.contentType]; } - (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response @@ -307,7 +307,7 @@ AssertEqual([request valueOfProperty: @"User-Agent"], @"BLIPConnectionTester"); AssertEq([[request valueOfProperty: @"Size"] intValue], size); - [request respondWithData: body]; + [request respondWithData: body contentType: request.contentType]; } diff -r 8267d5c429c4 -r 9fdd8dba529c IPAddress.h --- a/IPAddress.h Sat May 24 13:26:02 2008 -0700 +++ b/IPAddress.h Sat May 24 17:25:06 2008 -0700 @@ -9,7 +9,7 @@ #import -/** Represents an Internet Protocol address and port number (similar to a sockaddr_in.) +/** Represents an Internet Protocol address and port number (similar to a sockaddr_in). IPAddress itself only remembers the raw 32-bit IPv4 address; the subclass HostAddress also remembers the DNS host-name. */ @interface IPAddress : NSObject @@ -35,7 +35,7 @@ /** Initializes an IPAddress from a BSD struct sockaddr. */ - (id) initWithSockAddr: (const struct sockaddr*)sockaddr; -/** Returns the IP address of this host (with a socket number of zero.) +/** Returns the IP address of this host (with a port number of zero). If multiple network interfaces are active, the main one's address is returned. */ + (IPAddress*) localAddress; diff -r 8267d5c429c4 -r 9fdd8dba529c MYNetwork.xcodeproj/project.pbxproj --- a/MYNetwork.xcodeproj/project.pbxproj Sat May 24 13:26:02 2008 -0700 +++ b/MYNetwork.xcodeproj/project.pbxproj Sat May 24 17:25:06 2008 -0700 @@ -93,6 +93,7 @@ 270462C00DE4A639003D9D3F /* MYUtilities_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYUtilities_Prefix.pch; sourceTree = ""; }; 270462C10DE4A64B003D9D3F /* MYUtilitiesTest_main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYUtilitiesTest_main.m; sourceTree = ""; }; 270462C30DE4A65B003D9D3F /* BLIP Overview.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "BLIP Overview.txt"; path = "BLIP/BLIP Overview.txt"; sourceTree = ""; wrapsLines = 1; }; + 277903830DE8C2DD00C6D295 /* maindocs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = maindocs.h; sourceTree = ""; wrapsLines = 1; }; 27D5EC050DE5FEDE00CD84FA /* BLIPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPRequest.h; sourceTree = ""; }; 27D5EC060DE5FEDE00CD84FA /* BLIPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPRequest.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -115,6 +116,7 @@ isa = PBXGroup; children = ( 270462C30DE4A65B003D9D3F /* BLIP Overview.txt */, + 277903830DE8C2DD00C6D295 /* maindocs.h */, 270460F00DE49030003D9D3F /* MYNetwork */, 270461220DE49055003D9D3F /* MYUtilities */, 2704616D0DE492C9003D9D3F /* google-toolbox */, diff -r 8267d5c429c4 -r 9fdd8dba529c TCP/TCPConnection.h --- a/TCP/TCPConnection.h Sat May 24 13:26:02 2008 -0700 +++ b/TCP/TCPConnection.h Sat May 24 17:25:06 2008 -0700 @@ -23,7 +23,8 @@ /** A generic class that manages a TCP socket connection. It creates a TCPReader and a TCPWriter to handle I/O. - TCPConnection itself mostly deals with SSL setup and opening/closing the socket. */ + TCPConnection itself mostly deals with SSL setup and opening/closing the socket. + (The SSL related methods are inherited from TCPEndpoint.) */ @interface TCPConnection : TCPEndpoint { @private @@ -108,7 +109,8 @@ -/** The delegate messages sent by TCPConnection. */ +/** The delegate messages sent by TCPConnection. + All methods are optional. */ @protocol TCPConnectionDelegate @optional /** Called after the connection successfully opens. */ diff -r 8267d5c429c4 -r 9fdd8dba529c TCP/TCPListener.h --- a/TCP/TCPListener.h Sat May 24 13:26:02 2008 -0700 +++ b/TCP/TCPListener.h Sat May 24 17:25:06 2008 -0700 @@ -11,11 +11,17 @@ /** Generic TCP-based server that listens for incoming connections on a port. + For each incoming connection, it creates an instance of (a subclass of) the generic TCP client class TCPClient. The -connectionClass property lets you customize which subclass to use. - TCPListener supports Bonjour advertisements for the service, and automatic port renumbering - if there are conflicts. */ + + TCPListener supports SSL, Bonjour advertisements for the service, and automatic port renumbering + if there are conflicts. (The SSL related methods are inherited from TCPEndpoint.) + + You will almost always need to implement the TCPListenerDelegate protocol in your own + code, and set an instance as the listener's delegate property, in order to be informed + of important events such as incoming connections. */ @interface TCPListener : TCPEndpoint { @private @@ -40,6 +46,8 @@ /** The subclass of TCPConnection that will be instantiated. */ @property Class connectionClass; +/** Delegate object that will be called when interesting things happen to the listener -- + most importantly, when a new incoming connection is accepted. */ @property (assign) id delegate; /** Should the server listen for IPv6 connections (on the same port number)? Defaults to NO. */ @@ -58,6 +66,9 @@ changes are ignored while the server is open.) */ - (BOOL) open: (NSError **)error; +/** Opens the server, without returning a specific error code. + (In case of error the delegate's -listener:failedToOpen: method will be called with the + error code, anyway.) */ - (BOOL) open; /** Closes the server. */ @@ -94,14 +105,30 @@ #pragma mark - -/** The delegate messages sent by TCPListener. */ +/** The delegate messages sent by TCPListener. + All are optional except -listener:didAcceptConnection:. */ @protocol TCPListenerDelegate +/** Called after an incoming connection arrives and is opened; + the connection is now ready to send and receive data. + To control whether or not a connection should be accepted, implement the + -listener:shouldAcceptConnectionFrom: method. + To use a different class than TCPConnection, set the listener's -connectionClass property. + (This is the only required delegate method; the others are optional to implement.) */ - (void) listener: (TCPListener*)listener didAcceptConnection: (TCPConnection*)connection; @optional +/** Called after the listener successfully opens. */ - (void) listenerDidOpen: (TCPListener*)listener; +/** Called if the listener fails to open due to an error. */ - (void) listener: (TCPListener*)listener failedToOpen: (NSError*)error; +/** Called after the listener closes. */ - (void) listenerDidClose: (TCPListener*)listener; +/** Called when an incoming connection request arrives, but before the conncetion is opened; + return YES to accept the connection, NO to refuse it. + This method can only use criteria like the peer IP address, or the number of currently + open connections, to determine whether to accept. If you also want to check the + peer's SSL certificate, then return YES from this method, and use the TCPConnection + delegate method -connection:authorizeSSLPeer: to examine the certificate. */ - (BOOL) listener: (TCPListener*)listener shouldAcceptConnectionFrom: (IPAddress*)address; @end diff -r 8267d5c429c4 -r 9fdd8dba529c TCP/TCPStream.h --- a/TCP/TCPStream.h Sat May 24 13:26:02 2008 -0700 +++ b/TCP/TCPStream.h Sat May 24 17:25:06 2008 -0700 @@ -10,7 +10,7 @@ @class TCPConnection, TCPWriter; -/** INTERNAL abstract superclass for data streams, used by TCPConnection. */ +/** Abstract superclass for data streams, used by TCPConnection. */ @interface TCPStream : NSObject { TCPConnection *_conn; @@ -55,8 +55,15 @@ /** Input stream for a TCPConnection. */ @interface TCPReader : TCPStream + /** The connection's TCPWriter. */ @property (readonly) TCPWriter *writer; + +/** Reads bytes from the stream, like the corresponding method of NSInputStream. + The number of bytes actually read is returned, or zero if no data is available. + If an error occurs, it will call its -_gotError method, and return a negative number. */ +- (NSInteger) read: (void*)dst maxLength: (NSUInteger)maxLength; + @end diff -r 8267d5c429c4 -r 9fdd8dba529c TCP/TCPStream.m --- a/TCP/TCPStream.m Sat May 24 13:26:02 2008 -0700 +++ b/TCP/TCPStream.m Sat May 24 17:25:06 2008 -0700 @@ -239,6 +239,15 @@ } +- (NSInteger) read: (void*)dst maxLength: (NSUInteger)maxLength +{ + NSInteger bytesRead = [(NSInputStream*)_stream read:dst maxLength: maxLength]; + if( bytesRead < 0 ) + [self _gotError]; + return bytesRead; +} + + @end diff -r 8267d5c429c4 -r 9fdd8dba529c TCP/TCPWriter.h --- a/TCP/TCPWriter.h Sat May 24 13:26:02 2008 -0700 +++ b/TCP/TCPWriter.h Sat May 24 17:25:06 2008 -0700 @@ -9,7 +9,7 @@ #import "TCPStream.h" -/** INTERNAL class that writes a queue of arbitrary data blobs to the socket. */ +/** Output stream for a TCPConnection. Writes a queue of arbitrary data blobs to the socket. */ @interface TCPWriter : TCPStream { NSMutableArray *_queue; diff -r 8267d5c429c4 -r 9fdd8dba529c maindocs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/maindocs.h Sat May 24 17:25:06 2008 -0700 @@ -0,0 +1,53 @@ +// +// maindocs.h +// MYNetwork +// +// Created by Jens Alfke on 5/24/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// +// This file just contains the Doxygen comments that generate the main (index.html) page content. + + +/*! \mainpage MYNetwork: Mooseyard Networking library, including BLIP protocol implementation. + + \section intro_sec Introduction + + MYNetwork is a set of Objective-C networking classes for Cocoa applications on Mac OS X. + It consists of: +
    +
  • Networking utility classes (presently only IPAddress); +
  • A generic TCP client/server implementation, + useful for implementing your own network protocols; (see TCPListener and TCPConnection) +
  • An implementation of BLIP, a lightweight network protocol I've invented as an easy way + to send request and response messages between peers. (see BLIPListener, BLIPConnection, BLIPRequest, etc.) +
+ + MYNetwork is released under a BSD license, which means you can freely use it in open-source + or commercial projects, provided you give credit in your documentation or About box. + + \section config Configuration + + MYNetwork requires Mac OS X 10.5 or later, since it uses Objective-C 2 features like + properties and for...in loops. + + MYNetwork uses my MYUtilities library. You'll need to have downloaded that library, and added + the necessary source files and headers to your project. See the MYNetwork Xcode project, + which contains the minimal set of MYUtilities files needed to build MYUtilities. (That project + has its search paths set up to assume that MYUtilities is in a directory next to MYNetwork.) + + \section download How To Get It + + + + Or if you're just looking: + + + + */