BROKEN COMMIT. Majority of code to handle closing has been added. Listeners do not close correctly.
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/1_0_to_1_1_diffs.diff Tue Jun 23 11:44:30 2009 -0700
1.3 @@ -0,0 +1,425 @@
1.4 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIPConnection.h
1.5 +--- a/BLIP/BLIPConnection.h Thu Jun 19 10:22:19 2008 -0700
1.6 ++++ b/BLIP/BLIPConnection.h Mon Jun 23 14:02:31 2008 -0700
1.7 +@@ -20,6 +20,7 @@
1.8 + @interface BLIPConnection : TCPConnection
1.9 + {
1.10 + BLIPDispatcher *_dispatcher;
1.11 ++ BOOL _blipClosing;
1.12 + }
1.13 +
1.14 + /** The delegate object that will be called when the connection opens, closes or receives messages. */
1.15 +@@ -73,6 +74,13 @@
1.16 + /** Called when a BLIPResponse (to one of your requests) is received from the peer.
1.17 + This is called <i>after</i> the response object's onComplete target, if any, is invoked.*/
1.18 + - (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response;
1.19 ++
1.20 ++/** Called when the peer wants to close the connection. Return YES to allow, NO to prevent. */
1.21 ++- (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection;
1.22 ++
1.23 ++/** Called if the peer refuses a close request.
1.24 ++ The typical error is kBLIPError_Forbidden. */
1.25 ++- (void) connection: (BLIPConnection*)connection closeRequestFailedWithError: (NSError*)error;
1.26 + @end
1.27 +
1.28 +
1.29 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIPConnection.m
1.30 +--- a/BLIP/BLIPConnection.m Thu Jun 19 10:22:19 2008 -0700
1.31 ++++ b/BLIP/BLIPConnection.m Mon Jun 23 14:02:31 2008 -0700
1.32 +@@ -8,6 +8,7 @@
1.33 +
1.34 + #import "BLIPConnection.h"
1.35 + #import "BLIP_Internal.h"
1.36 ++#import "TCP_Internal.h"
1.37 + #import "BLIPReader.h"
1.38 + #import "BLIPWriter.h"
1.39 + #import "BLIPDispatcher.h"
1.40 +@@ -15,6 +16,7 @@
1.41 + #import "Logging.h"
1.42 + #import "Test.h"
1.43 + #import "ExceptionUtils.h"
1.44 ++#import "Target.h"
1.45 +
1.46 +
1.47 + NSString* const BLIPErrorDomain = @"BLIP";
1.48 +@@ -33,10 +35,14 @@
1.49 + }
1.50 +
1.51 +
1.52 ++@interface BLIPConnection ()
1.53 ++- (void) _handleCloseRequest: (BLIPRequest*)request;
1.54 ++@end
1.55 +
1.56 +
1.57 + @implementation BLIPConnection
1.58 +
1.59 ++
1.60 + - (void) dealloc
1.61 + {
1.62 + [_dispatcher release];
1.63 +@@ -48,6 +54,11 @@
1.64 + - (id<BLIPConnectionDelegate>) delegate {return (id)_delegate;}
1.65 + - (void) setDelegate: (id<BLIPConnectionDelegate>)delegate {_delegate = delegate;}
1.66 +
1.67 ++
1.68 ++#pragma mark -
1.69 ++#pragma mark RECEIVING:
1.70 ++
1.71 ++
1.72 + - (BLIPDispatcher*) dispatcher
1.73 + {
1.74 + if( ! _dispatcher ) {
1.75 +@@ -58,11 +69,23 @@
1.76 + }
1.77 +
1.78 +
1.79 ++- (void) _dispatchMetaRequest: (BLIPRequest*)request
1.80 ++{
1.81 ++ NSString* profile = request.profile;
1.82 ++ if( [profile isEqualToString: kBLIPProfile_Bye] )
1.83 ++ [self _handleCloseRequest: request];
1.84 ++ else
1.85 ++ [request respondWithErrorCode: kBLIPError_NotFound message: @"Unknown meta profile"];
1.86 ++}
1.87 ++
1.88 ++
1.89 + - (void) _dispatchRequest: (BLIPRequest*)request
1.90 + {
1.91 + LogTo(BLIP,@"Received all of %@",request.descriptionWithProperties);
1.92 + @try{
1.93 +- if( ! [self.dispatcher dispatchMessage: request] )
1.94 ++ if( request._flags & kBLIP_Meta )
1.95 ++ [self _dispatchMetaRequest: request];
1.96 ++ else if( ! [self.dispatcher dispatchMessage: request] )
1.97 + [self tellDelegate: @selector(connection:receivedRequest:) withObject: request];
1.98 + if( ! request.noReply && ! request.repliedTo ) {
1.99 + LogTo(BLIP,@"Returning default empty response to %@",request);
1.100 +@@ -81,6 +104,10 @@
1.101 + }
1.102 +
1.103 +
1.104 ++#pragma mark -
1.105 ++#pragma mark SENDING:
1.106 ++
1.107 ++
1.108 + - (BLIPRequest*) request
1.109 + {
1.110 + return [[[BLIPRequest alloc] _initWithConnection: self body: nil properties: nil] autorelease];
1.111 +@@ -103,11 +130,61 @@
1.112 + }
1.113 +
1.114 +
1.115 ++#pragma mark -
1.116 ++#pragma mark CLOSING:
1.117 ++
1.118 ++
1.119 ++- (void) _beginClose
1.120 ++{
1.121 ++ // Override of TCPConnection method. Instead of closing the socket, send a 'bye' request:
1.122 ++ if( ! _blipClosing ) {
1.123 ++ LogTo(BLIPVerbose,@"Sending close request...");
1.124 ++ BLIPRequest *r = [self request];
1.125 ++ [r _setFlag: kBLIP_Meta value: YES];
1.126 ++ r.profile = kBLIPProfile_Bye;
1.127 ++ BLIPResponse *response = [r send];
1.128 ++ response.onComplete = $target(self,_receivedCloseResponse:);
1.129 ++ }
1.130 ++ // Put the writer in close mode, to prevent client from sending any more requests:
1.131 ++ [self.writer close];
1.132 ++}
1.133 ++
1.134 ++- (void) _receivedCloseResponse: (BLIPResponse*)response
1.135 ++{
1.136 ++ NSError *error = response.error;
1.137 ++ LogTo(BLIPVerbose,@"Received close response: error=%@",error);
1.138 ++ if( error ) {
1.139 ++ [self _unclose];
1.140 ++ [self tellDelegate: @selector(connection:closeRequestFailedWithError:) withObject: error];
1.141 ++ } else {
1.142 ++ // Now finally close the socket:
1.143 ++ [super _beginClose];
1.144 ++ }
1.145 ++}
1.146 ++
1.147 ++
1.148 ++- (void) _handleCloseRequest: (BLIPRequest*)request
1.149 ++{
1.150 ++ LogTo(BLIPVerbose,@"Received a close request");
1.151 ++ if( [_delegate respondsToSelector: @selector(connectionReceivedCloseRequest:)] )
1.152 ++ if( ! [_delegate connectionReceivedCloseRequest: self] ) {
1.153 ++ LogTo(BLIPVerbose,@"Responding with denial of close request");
1.154 ++ [request respondWithErrorCode: kBLIPError_Forbidden message: @"Close request denied"];
1.155 ++ return;
1.156 ++ }
1.157 ++
1.158 ++ LogTo(BLIPVerbose,@"Close request accepted");
1.159 ++ _blipClosing = YES; // this prevents _beginClose from sending a close request back
1.160 ++ [self close];
1.161 ++}
1.162 ++
1.163 ++
1.164 + @end
1.165 +
1.166 +
1.167 +
1.168 +
1.169 ++#pragma mark -
1.170 + @implementation BLIPListener
1.171 +
1.172 + - (id) initWithPort: (UInt16)port
1.173 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIPMessage.m
1.174 +--- a/BLIP/BLIPMessage.m Thu Jun 19 10:22:19 2008 -0700
1.175 ++++ b/BLIP/BLIPMessage.m Mon Jun 23 14:02:31 2008 -0700
1.176 +@@ -74,6 +74,8 @@
1.177 + [desc appendString: @", urgent"];
1.178 + if( _flags & kBLIP_NoReply )
1.179 + [desc appendString: @", noreply"];
1.180 ++ if( _flags & kBLIP_Meta )
1.181 ++ [desc appendString: @", META"];
1.182 + [desc appendString: @"]"];
1.183 + return desc;
1.184 + }
1.185 +@@ -103,6 +105,8 @@
1.186 + _flags &= ~flag;
1.187 + }
1.188 +
1.189 ++- (BLIPMessageFlags) _flags {return _flags;}
1.190 ++
1.191 + - (BOOL) compressed {return (_flags & kBLIP_Compressed) != 0;}
1.192 + - (BOOL) urgent {return (_flags & kBLIP_Urgent) != 0;}
1.193 + - (void) setCompressed: (BOOL)compressed {[self _setFlag: kBLIP_Compressed value: compressed];}
1.194 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIPReader.m
1.195 +--- a/BLIP/BLIPReader.m Thu Jun 19 10:22:19 2008 -0700
1.196 ++++ b/BLIP/BLIPReader.m Mon Jun 23 14:02:31 2008 -0700
1.197 +@@ -93,7 +93,7 @@
1.198 +
1.199 + - (BOOL) isBusy
1.200 + {
1.201 +- return _curBytesRead > 0;
1.202 ++ return _curBytesRead > 0 || _pendingRequests.count > 0 || _pendingResponses.count > 0;
1.203 + }
1.204 +
1.205 +
1.206 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIPRequest.m
1.207 +--- a/BLIP/BLIPRequest.m Thu Jun 19 10:22:19 2008 -0700
1.208 ++++ b/BLIP/BLIPRequest.m Mon Jun 23 14:02:31 2008 -0700
1.209 +@@ -199,6 +199,8 @@
1.210 + setObj(&_mutableBody,nil);
1.211 +
1.212 + BLIPMutableProperties *errorProps = [self.properties mutableCopy];
1.213 ++ if( ! errorProps )
1.214 ++ errorProps = [[BLIPMutableProperties alloc] init];
1.215 + NSDictionary *userInfo = error.userInfo;
1.216 + for( NSString *key in userInfo ) {
1.217 + id value = $castIf(NSString,[userInfo objectForKey: key]);
1.218 +@@ -227,8 +229,12 @@
1.219 + {
1.220 + Assert(_connection,@"%@ has no connection to send over",self);
1.221 + Assert(!_sent,@"%@ was already sent",self);
1.222 ++ BLIPWriter *writer = (BLIPWriter*)_connection.writer;
1.223 ++ Assert(writer,@"%@'s connection has no writer (already closed?)",self);
1.224 + [self _encode];
1.225 +- return (self.sent = [(BLIPWriter*)_connection.writer sendMessage: self]);
1.226 ++ BOOL sent = self.sent = [writer sendMessage: self];
1.227 ++ Assert(sent);
1.228 ++ return sent;
1.229 + }
1.230 +
1.231 +
1.232 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIPTest.m
1.233 +--- a/BLIP/BLIPTest.m Thu Jun 19 10:22:19 2008 -0700
1.234 ++++ b/BLIP/BLIPTest.m Mon Jun 23 14:02:31 2008 -0700
1.235 +@@ -35,6 +35,7 @@
1.236 + #define kClientUsesSSLCert NO
1.237 + #define kListenerRequiresSSL NO
1.238 + #define kListenerRequiresClientCert NO
1.239 ++#define kListenerCloseAfter 50
1.240 +
1.241 +
1.242 + static SecIdentityRef GetClientIdentity(void) {
1.243 +@@ -100,36 +101,38 @@
1.244 +
1.245 + - (void) sendAMessage
1.246 + {
1.247 +- if(_pending.count<100) {
1.248 +- Log(@"** Sending another %i messages...", kNBatchedMessages);
1.249 +- for( int i=0; i<kNBatchedMessages; i++ ) {
1.250 +- size_t size = random() % 32768;
1.251 +- NSMutableData *body = [NSMutableData dataWithLength: size];
1.252 +- UInt8 *bytes = body.mutableBytes;
1.253 +- for( size_t i=0; i<size; i++ )
1.254 +- bytes[i] = i % 256;
1.255 +-
1.256 +- BLIPRequest *q = [_conn requestWithBody: body
1.257 +- properties: $dict({@"Content-Type", @"application/octet-stream"},
1.258 +- {@"User-Agent", @"BLIPConnectionTester"},
1.259 +- {@"Date", [[NSDate date] description]},
1.260 +- {@"Size",$sprintf(@"%u",size)})];
1.261 +- Assert(q);
1.262 +- if( kUseCompression && (random()%2==1) )
1.263 +- q.compressed = YES;
1.264 +- if( random()%16 > 12 )
1.265 +- q.urgent = YES;
1.266 +- BLIPResponse *response = [q send];
1.267 +- Assert(response);
1.268 +- Assert(q.number>0);
1.269 +- Assert(response.number==q.number);
1.270 +- [_pending setObject: $object(size) forKey: $object(q.number)];
1.271 +- response.onComplete = $target(self,responseArrived:);
1.272 ++ if( _conn.status==kTCP_Open || _conn.status==kTCP_Opening ) {
1.273 ++ if(_pending.count<100) {
1.274 ++ Log(@"** Sending another %i messages...", kNBatchedMessages);
1.275 ++ for( int i=0; i<kNBatchedMessages; i++ ) {
1.276 ++ size_t size = random() % 32768;
1.277 ++ NSMutableData *body = [NSMutableData dataWithLength: size];
1.278 ++ UInt8 *bytes = body.mutableBytes;
1.279 ++ for( size_t i=0; i<size; i++ )
1.280 ++ bytes[i] = i % 256;
1.281 ++
1.282 ++ BLIPRequest *q = [_conn requestWithBody: body
1.283 ++ properties: $dict({@"Content-Type", @"application/octet-stream"},
1.284 ++ {@"User-Agent", @"BLIPConnectionTester"},
1.285 ++ {@"Date", [[NSDate date] description]},
1.286 ++ {@"Size",$sprintf(@"%u",size)})];
1.287 ++ Assert(q);
1.288 ++ if( kUseCompression && (random()%2==1) )
1.289 ++ q.compressed = YES;
1.290 ++ if( random()%16 > 12 )
1.291 ++ q.urgent = YES;
1.292 ++ BLIPResponse *response = [q send];
1.293 ++ Assert(response);
1.294 ++ Assert(q.number>0);
1.295 ++ Assert(response.number==q.number);
1.296 ++ [_pending setObject: $object(size) forKey: $object(q.number)];
1.297 ++ response.onComplete = $target(self,responseArrived:);
1.298 ++ }
1.299 ++ } else {
1.300 ++ Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count);
1.301 + }
1.302 +- } else {
1.303 +- Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count);
1.304 ++ [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval];
1.305 + }
1.306 +- [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval];
1.307 + }
1.308 +
1.309 + - (void) responseArrived: (BLIPResponse*)response
1.310 +@@ -191,6 +194,13 @@
1.311 + Log(@"Now %u replies pending", _pending.count);
1.312 + }
1.313 +
1.314 ++- (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection
1.315 ++{
1.316 ++ BOOL response = NO;
1.317 ++ Log(@"***** %@ received a close request; returning %i",connection,response);
1.318 ++ return response;
1.319 ++}
1.320 ++
1.321 +
1.322 + @end
1.323 +
1.324 +@@ -217,6 +227,7 @@
1.325 + @interface BLIPTestListener : NSObject <TCPListenerDelegate, BLIPConnectionDelegate>
1.326 + {
1.327 + BLIPListener *_listener;
1.328 ++ int _nReceived;
1.329 + }
1.330 +
1.331 + @end
1.332 +@@ -277,6 +288,7 @@
1.333 + - (void) connectionDidOpen: (TCPConnection*)connection
1.334 + {
1.335 + Log(@"** %@ didOpen [SSL=%@]",connection,connection.actualSecurityLevel);
1.336 ++ _nReceived = 0;
1.337 + }
1.338 + - (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
1.339 + {
1.340 +@@ -312,6 +324,22 @@
1.341 + AssertEq([[request valueOfProperty: @"Size"] intValue], size);
1.342 +
1.343 + [request respondWithData: body contentType: request.contentType];
1.344 ++
1.345 ++ if( ++ _nReceived == kListenerCloseAfter ) {
1.346 ++ Log(@"********** Closing BLIPTestListener after %i requests",_nReceived);
1.347 ++ [connection close];
1.348 ++ }
1.349 ++}
1.350 ++
1.351 ++- (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection;
1.352 ++{
1.353 ++ Log(@"***** %@ received a close request",connection);
1.354 ++ return YES;
1.355 ++}
1.356 ++
1.357 ++- (void) connection: (BLIPConnection*)connection closeRequestFailedWithError: (NSError*)error
1.358 ++{
1.359 ++ Log(@"***** %@'s close request failed: %@",connection,error);
1.360 + }
1.361 +
1.362 +
1.363 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIPWriter.m
1.364 +--- a/BLIP/BLIPWriter.m Thu Jun 19 10:22:19 2008 -0700
1.365 ++++ b/BLIP/BLIPWriter.m Mon Jun 23 14:02:31 2008 -0700
1.366 +@@ -79,10 +79,6 @@
1.367 +
1.368 + - (BOOL) sendMessage: (BLIPMessage*)message
1.369 + {
1.370 +- if( _shouldClose ) {
1.371 +- Warn(@"%@: Attempt to send a message after the connection has started closing",self);
1.372 +- return NO;
1.373 +- }
1.374 + Assert(!message.sent,@"message has already been sent");
1.375 + [self _queueMessage: message isNew: YES];
1.376 + return YES;
1.377 +@@ -91,12 +87,14 @@
1.378 +
1.379 + - (BOOL) sendRequest: (BLIPRequest*)q response: (BLIPResponse*)response
1.380 + {
1.381 +- if( !_shouldClose ) {
1.382 +- [q _assignedNumber: ++_numRequestsSent];
1.383 +- if( response ) {
1.384 +- [response _assignedNumber: _numRequestsSent];
1.385 +- [(BLIPReader*)self.reader _addPendingResponse: response];
1.386 +- }
1.387 ++ if( _shouldClose ) {
1.388 ++ Warn(@"%@: Attempt to send a request after the connection has started closing: %@",self,q);
1.389 ++ return NO;
1.390 ++ }
1.391 ++ [q _assignedNumber: ++_numRequestsSent];
1.392 ++ if( response ) {
1.393 ++ [response _assignedNumber: _numRequestsSent];
1.394 ++ [(BLIPReader*)self.reader _addPendingResponse: response];
1.395 + }
1.396 + return [self sendMessage: q];
1.397 + }
1.398 +diff -r 70590cc555aa -r 16454d63d4c2 BLIP/BLIP_Internal.h
1.399 +--- a/BLIP/BLIP_Internal.h Thu Jun 19 10:22:19 2008 -0700
1.400 ++++ b/BLIP/BLIP_Internal.h Mon Jun 23 14:02:31 2008 -0700
1.401 +@@ -29,6 +29,7 @@
1.402 + kBLIP_Urgent = 0x0020, // please send sooner/faster
1.403 + kBLIP_NoReply = 0x0040, // no RPY needed
1.404 + kBLIP_MoreComing= 0x0080, // More frames coming (Applies only to individual frame)
1.405 ++ kBLIP_Meta = 0x0100, // Special message type, handled internally (hello, bye, ...)
1.406 + };
1.407 + typedef UInt16 BLIPMessageFlags;
1.408 +
1.409 +@@ -41,7 +42,10 @@
1.410 + UInt16 size; // total size of frame, _including_ this header
1.411 + } BLIPFrameHeader;
1.412 +
1.413 +-#define kBLIPFrameHeaderMagicNumber 0x9B34F205
1.414 ++#define kBLIPFrameHeaderMagicNumber 0x9B34F206
1.415 ++
1.416 ++#define kBLIPProfile_Hi @"Hi" // Used for Profile header in meta greeting message
1.417 ++#define kBLIPProfile_Bye @"Bye" // Used for Profile header in meta close-request message
1.418 +
1.419 +
1.420 + @interface BLIPConnection ()
1.421 +@@ -52,6 +56,7 @@
1.422 +
1.423 + @interface BLIPMessage ()
1.424 + @property BOOL sent, propertiesAvailable, complete;
1.425 ++- (BLIPMessageFlags) _flags;
1.426 + - (void) _setFlag: (BLIPMessageFlags)flag value: (BOOL)value;
1.427 + - (void) _encode;
1.428 + @end
2.1 --- a/Python/BLIP.py Sun May 24 15:03:39 2009 -0700
2.2 +++ b/Python/BLIP.py Tue Jun 23 11:44:30 2009 -0700
2.3 @@ -27,7 +27,7 @@
2.4
2.5 # INTERNAL CONSTANTS -- NO TOUCHIES!
2.6
2.7 -kFrameMagicNumber = 0x9B34F205
2.8 +kFrameMagicNumber = 0x9B34F206
2.9 kFrameHeaderFormat = '!LLHH'
2.10 kFrameHeaderSize = 12
2.11
2.12 @@ -36,11 +36,15 @@
2.13 kMsgFlag_Urgent = 0x0020
2.14 kMsgFlag_NoReply = 0x0040
2.15 kMsgFlag_MoreComing = 0x0080
2.16 +kMsgFlag_Meta = 0x0100
2.17
2.18 kMsgType_Request = 0
2.19 kMsgType_Response = 1
2.20 kMsgType_Error = 2
2.21
2.22 +kMsgProfile_Hi = "Hi"
2.23 +kMsgProfile_Bye = "Bye"
2.24 +
2.25
2.26 log = logging.getLogger('BLIP')
2.27 log.propagate = True
2.28 @@ -104,7 +108,7 @@
2.29 self.connect(address)
2.30 self.address = address
2.31 self.listener = listener
2.32 - self.onRequest = None
2.33 + self.onRequest = self.onCloseRequest = self.onCloseRefused = None
2.34 self.pendingRequests = {}
2.35 self.pendingResponses = {}
2.36 self.outBox = []
2.37 @@ -112,12 +116,7 @@
2.38 self.inNumRequests = self.outNumRequests = 0
2.39 self.sending = False
2.40 self._endOfFrame()
2.41 -
2.42 - def close(self):
2.43 - if self.status > kClosed:
2.44 - self.status = kClosing
2.45 - log.info("Connection closing...")
2.46 - asynchat.async_chat.close(self)
2.47 + self._closeWhenPossible = False
2.48
2.49 def handle_connect(self):
2.50 log.info("Connection open!")
2.51 @@ -130,25 +129,19 @@
2.52 self.status = kDisconnected
2.53 self.close()
2.54
2.55 - def handle_close(self):
2.56 - log.info("Connection closed!")
2.57 - self.pendingRequests = self.pendingResponses = None
2.58 - self.outBox = None
2.59 - if self.status == kClosing:
2.60 - self.status = kClosed
2.61 - else:
2.62 - self.status = kDisconnected
2.63 - asynchat.async_chat.handle_close(self)
2.64 -
2.65
2.66 ### SENDING:
2.67
2.68 @property
2.69 - def canSend(self):
2.70 + def isOpen(self):
2.71 return self.status==kOpening or self.status==kOpen
2.72
2.73 + @property
2.74 + def canSend(self):
2.75 + return self.isOpen and not self._closeWhenPossible
2.76 +
2.77 def _sendMessage(self, msg):
2.78 - if self.canSend:
2.79 + if self.isOpen:
2.80 self._outQueueMessage(msg,True)
2.81 if not self.sending:
2.82 log.debug("Waking up the output stream")
2.83 @@ -208,6 +201,7 @@
2.84 else:
2.85 log.debug("Nothing more to send")
2.86 self.sending = False
2.87 + self._closeIfReady()
2.88 return None
2.89
2.90 ### RECEIVING:
2.91 @@ -255,6 +249,7 @@
2.92 self.inNumRequests += 1
2.93 elif msgType==kMsgType_Response or msgType==kMsgType_Error:
2.94 message = self.pendingResponses.get(requestNo)
2.95 + message._updateFlags(flags)
2.96
2.97 if message != None:
2.98 message._beginFrame(flags)
2.99 @@ -284,10 +279,88 @@
2.100 try:
2.101 msg._finished()
2.102 if not msg.isResponse:
2.103 - self.onRequest(msg)
2.104 + if msg._meta:
2.105 + self._dispatchMetaRequest(msg)
2.106 + else:
2.107 + self.onRequest(msg)
2.108 except Exception, x:
2.109 log.error("Exception handling incoming message: %s", traceback.format_exc())
2.110 #FIX: Send an error reply
2.111 + # Check to see if we're done and ready to close:
2.112 + self._closeIfReady()
2.113 +
2.114 + def _dispatchMetaRequest(self, request):
2.115 + """Handles dispatching internal meta requests."""
2.116 + if request['Profile'] == kMsgProfile_Bye:
2.117 + shouldClose = True
2.118 + if self.onCloseRequest:
2.119 + shouldClose = self.onCloseRequest()
2.120 + if not shouldClose:
2.121 + log.debug("Sending resfusal to close...")
2.122 + response = request.response
2.123 + response.isError = True
2.124 + response['Error-Domain'] = "BLIP"
2.125 + response['Error-Code'] = 403
2.126 + response.body = "Close request denied"
2.127 + response.send()
2.128 + else:
2.129 + log.debug("Sending permission to close...")
2.130 + response = request.response
2.131 + response.send()
2.132 + else:
2.133 + response = request.response
2.134 + response.isError = True
2.135 + response['Error-Domain'] = "BLIP"
2.136 + response['Error-Code'] = 404
2.137 + response.body = "Unknown meta profile"
2.138 + response.send()
2.139 +
2.140 + ### CLOSING:
2.141 +
2.142 + def close(self):
2.143 + """Publicly callable close method. Sends close request to peer."""
2.144 + if self.status != kOpen:
2.145 + return False
2.146 + log.info("Sending close request...")
2.147 + req = OutgoingRequest(self, None, {'Profile': kMsgProfile_Bye})
2.148 + req._meta = True
2.149 + req.response.onComplete = self._handleCloseResponse
2.150 + if not req.send():
2.151 + log.error("Error sending close request.")
2.152 + return False
2.153 + else:
2.154 + self.status = kClosing
2.155 + return True
2.156 +
2.157 + def _handleCloseResponse(self, response):
2.158 + """Called when we receive a response to a close request."""
2.159 + log.info("Received close response.")
2.160 + if response.isError:
2.161 + # remote refused to close
2.162 + if self.onCloseRefused:
2.163 + self.onCloseRefused(response)
2.164 + self.status = kOpen
2.165 + else:
2.166 + # now wait until everything has finished sending, then actually close
2.167 + log.info("No refusal, actually closing...")
2.168 + self._closeWhenPossible = True
2.169 +
2.170 + def _closeIfReady(self):
2.171 + """Checks if all transmissions are complete and then closes the actual socket."""
2.172 + if self._closeWhenPossible and len(self.outBox) == 0 and len(self.pendingRequests) == 0 and len(self.pendingResponses) == 0:
2.173 + # self._closeWhenPossible = False
2.174 + log.debug("_closeIfReady closing.")
2.175 + asynchat.async_chat.close(self)
2.176 +
2.177 + def handle_close(self):
2.178 + """Called when the socket actually closes."""
2.179 + log.info("Connection closed!")
2.180 + self.pendingRequests = self.pendingResponses = None
2.181 + self.outBox = None
2.182 + if self.status == kClosing:
2.183 + self.status = kClosed
2.184 + else:
2.185 + self.status = kDisconnected
2.186
2.187
2.188 ### MESSAGE CLASSES:
2.189 @@ -305,13 +378,17 @@
2.190 @property
2.191 def flags(self):
2.192 if self.isResponse:
2.193 - flags = kMsgType_Response
2.194 + if self.isError:
2.195 + flags = kMsgType_Error
2.196 + else:
2.197 + flags = kMsgType_Response
2.198 else:
2.199 flags = kMsgType_Request
2.200 if self.urgent: flags |= kMsgFlag_Urgent
2.201 if self.compressed: flags |= kMsgFlag_Compressed
2.202 if self.noReply: flags |= kMsgFlag_NoReply
2.203 if self._moreComing:flags |= kMsgFlag_MoreComing
2.204 + if self._meta: flags |= kMsgFlag_Meta
2.205 return flags
2.206
2.207 def __str__(self):
2.208 @@ -322,6 +399,7 @@
2.209 if self.compressed: s += " CMP"
2.210 if self.noReply: s += " NOR"
2.211 if self._moreComing:s += " MOR"
2.212 + if self._meta: s += " MET"
2.213 if self.body: s += " %i bytes" %len(self.body)
2.214 return s+"]"
2.215
2.216 @@ -352,11 +430,16 @@
2.217 def __init__(self, connection, requestNo, flags):
2.218 super(IncomingMessage,self).__init__(connection)
2.219 self.requestNo = requestNo
2.220 + self._updateFlags(flags)
2.221 + self.frames = []
2.222 +
2.223 + def _updateFlags(self, flags):
2.224 self.urgent = (flags & kMsgFlag_Urgent) != 0
2.225 self.compressed = (flags & kMsgFlag_Compressed) != 0
2.226 self.noReply = (flags & kMsgFlag_NoReply) != 0
2.227 self._moreComing= (flags & kMsgFlag_MoreComing) != 0
2.228 - self.frames = []
2.229 + self._meta = (flags & kMsgFlag_Meta) != 0
2.230 + self.isError = (flags & kMsgType_Error) != 0
2.231
2.232 def _beginFrame(self, flags):
2.233 """Received a frame header."""
2.234 @@ -377,16 +460,18 @@
2.235 if propSize>len(encoded): raise MessageException, "properties too long to fit"
2.236 if propSize>2 and encoded[propSize-1] != '\000': raise MessageException, "properties are not nul-terminated"
2.237
2.238 - proplist = encoded[2:propSize-1].split('\000')
2.239 + if propSize > 2:
2.240 + proplist = encoded[2:propSize-1].split('\000')
2.241 +
2.242 + if len(proplist) & 1: raise MessageException, "odd number of property strings"
2.243 + for i in xrange(0,len(proplist),2):
2.244 + def expand(str):
2.245 + if len(str)==1:
2.246 + str = IncomingMessage.__expandDict.get(str,str)
2.247 + return str
2.248 + self.properties[ expand(proplist[i])] = expand(proplist[i+1])
2.249 +
2.250 encoded = encoded[propSize:]
2.251 - if len(proplist) & 1: raise MessageException, "odd number of property strings"
2.252 - for i in xrange(0,len(proplist),2):
2.253 - def expand(str):
2.254 - if len(str)==1:
2.255 - str = IncomingMessage.__expandDict.get(str,str)
2.256 - return str
2.257 - self.properties[ expand(proplist[i])] = expand(proplist[i+1])
2.258 -
2.259 # Decode the body:
2.260 if self.compressed and len(encoded)>0:
2.261 try:
2.262 @@ -411,7 +496,7 @@
2.263
2.264 def __init__(self, connection, body=None, properties=None):
2.265 Message.__init__(self,connection,body,properties)
2.266 - self.urgent = self.compressed = self.noReply = False
2.267 + self.urgent = self.compressed = self.noReply = self._meta = self.isError = False
2.268 self._moreComing = True
2.269
2.270 def __setitem__(self, key,val):
2.271 @@ -435,7 +520,7 @@
2.272 propertiesSize = out.tell()
2.273 assert propertiesSize<65536 #FIX: Return an error instead
2.274
2.275 - body = self.body
2.276 + body = self.body or ""
2.277 if self.compressed:
2.278 z = zlib.compressobj(6,zlib.DEFLATED,31) # window size of 31 needed for gzip format
2.279 out.write(z.compress(body))
3.1 --- a/Python/BLIPConnectionTest.py Sun May 24 15:03:39 2009 -0700
3.2 +++ b/Python/BLIPConnectionTest.py Tue Jun 23 11:44:30 2009 -0700
3.3 @@ -72,7 +72,8 @@
3.4
3.5 def tearDown(self):
3.6 self.connection.close()
3.7 + asyncore.loop() # got to give it time to negotiate close; this call should exit eventually
3.8
3.9 if __name__ == '__main__':
3.10 - logging.basicConfig(level=logging.INFO)
3.11 + logging.basicConfig(level=logging.DEBUG)
3.12 unittest.main()
4.1 --- a/Python/BLIPListenerTest.py Sun May 24 15:03:39 2009 -0700
4.2 +++ b/Python/BLIPListenerTest.py Tue Jun 23 11:44:30 2009 -0700
4.3 @@ -42,5 +42,5 @@
4.4 logging.info("KeyboardInterrupt")
4.5
4.6 if __name__ == '__main__':
4.7 - logging.basicConfig(level=logging.INFO)
4.8 + logging.basicConfig(level=logging.DEBUG)
4.9 unittest.main()
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/Python/CloseTestPing.py Tue Jun 23 11:44:30 2009 -0700
5.3 @@ -0,0 +1,36 @@
5.4 +# CloseTestPing.py
5.5 +# Tests the closing negotiation facilities of the BLIP 1.1 protocol
5.6 +
5.7 +from BLIP import Connection, OutgoingRequest
5.8 +
5.9 +import unittest
5.10 +import asyncore
5.11 +import logging
5.12 +
5.13 +class CloseTestPing(unittest.TestCase):
5.14 +
5.15 + def handleCloseRefusal(self, resp):
5.16 + logging.info("Close request was refused!")
5.17 +
5.18 + def setUp(self):
5.19 + self.connection = Connection( ('localhost', 1337) )
5.20 + self.connection.onCloseRefused = self.handleCloseRefusal
5.21 +
5.22 + def handleResponse(self, resp):
5.23 + logging.info("Got response...")
5.24 +
5.25 + def testClose(self):
5.26 + req = OutgoingRequest(self.connection, "Ping")
5.27 + req.response.onComplete = self.handleResponse
5.28 + req.send()
5.29 +
5.30 + asyncore.loop(timeout=1, count=5)
5.31 +
5.32 + self.connection.close()
5.33 +
5.34 + asyncore.loop()
5.35 +
5.36 +
5.37 +if __name__ == '__main__':
5.38 + logging.basicConfig(level=logging.DEBUG)
5.39 + unittest.main()
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/Python/CloseTestPong.py Tue Jun 23 11:44:30 2009 -0700
6.3 @@ -0,0 +1,38 @@
6.4 +# CloseTestPong.py
6.5 +# Tests the closing negotiation facilities of the BLIP 1.1 protocol
6.6 +
6.7 +from BLIP import Listener
6.8 +
6.9 +import logging
6.10 +import asyncore
6.11 +import unittest
6.12 +
6.13 +class CloseTestPong(unittest.TestCase):
6.14 +
6.15 + def shouldClose(self):
6.16 + logging.info("Allowed to close.")
6.17 + return True
6.18 +
6.19 + def handleConnection(self, conn):
6.20 + logging.info("Accepted connection.")
6.21 + conn.onCloseRequest = self.shouldClose
6.22 +
6.23 + def handleRequest(self, req):
6.24 + resp = req.response
6.25 + resp.body = "Pong"
6.26 + resp.send()
6.27 +
6.28 + def testClose(self):
6.29 + listen = Listener(1337)
6.30 + listen.onConnected = self.handleConnection
6.31 + listen.onRequest = self.handleRequest
6.32 +
6.33 + try:
6.34 + asyncore.loop()
6.35 + except KeyboardInterrupt:
6.36 + pass
6.37 +
6.38 +
6.39 +if __name__ == '__main__':
6.40 + logging.basicConfig(level=logging.DEBUG)
6.41 + unittest.main()
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/Python/asynchatPing.py Tue Jun 23 11:44:30 2009 -0700
7.3 @@ -0,0 +1,60 @@
7.4 +# asynchatPing
7.5 +# Uses asynchat
7.6 +# Not related to BLIP - just to aid in my understanding of what's going on
7.7 +# Sends "Ping", waits for "Pong"
7.8 +
7.9 +import socket
7.10 +import asyncore
7.11 +import asynchat
7.12 +
7.13 +kNumPings = 10
7.14 +
7.15 +class asynchatPing(asynchat.async_chat):
7.16 + def __init__(self, address):
7.17 + asynchat.async_chat.__init__(self)
7.18 + self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
7.19 + self.connect(address)
7.20 + self.set_terminator("Pong")
7.21 + self.pingsSent = self.pongsGot = 0
7.22 + self.donePing = self.donePong = False
7.23 +
7.24 + def handle_connect(self):
7.25 + print "Connected"
7.26 +
7.27 + def handle_close(self):
7.28 + print "Closed"
7.29 + asynchat.async_chat.handle_close(self)
7.30 +
7.31 + def collect_incoming_data(self, data):
7.32 + """discard data"""
7.33 + pass
7.34 +
7.35 + def found_terminator(self):
7.36 + """when we get a Pong"""
7.37 + print "Received 'Pong'"
7.38 + self.pongsGot += 1
7.39 + if self.pongsGot == kNumPings:
7.40 + print "Done ponging"
7.41 + self.donePong = True
7.42 + self.close_when_done()
7.43 +
7.44 + def ping(self):
7.45 + if not self.donePing:
7.46 + self.push("Ping")
7.47 + print "Sent 'Ping'"
7.48 + self.pingsSent += 1
7.49 + if self.pingsSent == kNumPings:
7.50 + print "Done pinging"
7.51 + self.donePing = True
7.52 +
7.53 + def run(self):
7.54 + timeout = 0
7.55 + while not self.donePing:
7.56 + self.ping()
7.57 + asyncore.loop(timeout=timeout, count=1)
7.58 + asyncore.loop()
7.59 + print "Done!"
7.60 +
7.61 +if __name__ == '__main__':
7.62 + ping = asynchatPing( ('localhost', 1337) )
7.63 + ping.run()
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/Python/asynchatPong.py Tue Jun 23 11:44:30 2009 -0700
8.3 @@ -0,0 +1,59 @@
8.4 +# asynchatPong
8.5 +# Listener using asynchat
8.6 +# Not related to BLIP - just to aid in my understanding of what's going on
8.7 +# Sends "Pong" when it gets "Ping"
8.8 +
8.9 +import sys
8.10 +import traceback
8.11 +import socket
8.12 +import asyncore
8.13 +import asynchat
8.14 +
8.15 +class asynchatPongListener(asyncore.dispatcher):
8.16 + def __init__(self, port):
8.17 + asyncore.dispatcher.__init__(self)
8.18 + self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
8.19 + self.bind( ('', port) )
8.20 + self.listen(2)
8.21 + self.shouldAccept = True
8.22 +
8.23 + def handle_accept(self):
8.24 + if self.shouldAccept:
8.25 + sock, addr = self.accept()
8.26 + self.conn = asynchatPong(sock, self)
8.27 + self.shouldAccept = False
8.28 +
8.29 + def handle_error(self):
8.30 + (typ,val,trace) = sys.exc_info()
8.31 + print "Listener caught: %s %s\n%s" % (typ,val,traceback.format_exc())
8.32 + self.close()
8.33 +
8.34 + def handle_close(self):
8.35 + print "Listener got close"
8.36 + asyncore.dispatcher.handle_close(self)
8.37 +
8.38 +class asynchatPong(asynchat.async_chat):
8.39 + def __init__(self, socket, listener):
8.40 + asynchat.async_chat.__init__(self, socket)
8.41 + self._listener = listener
8.42 + self.set_terminator("Ping")
8.43 +
8.44 + def collect_incoming_data(self, data):
8.45 + """called when arbitrary amount of data arrives. we just eat it"""
8.46 + pass
8.47 +
8.48 + def found_terminator(self):
8.49 + """called when the terminator we set is found"""
8.50 + print "Found 'Ping'"
8.51 + self.push("Pong")
8.52 + print "Sent 'Pong'"
8.53 +
8.54 + def handle_close(self):
8.55 + print "Closed; closing listener"
8.56 + self._listener.close()
8.57 + asynchat.async_chat.handle_close(self)
8.58 +
8.59 +
8.60 +if __name__ == '__main__':
8.61 + pong = asynchatPongListener(1337)
8.62 + asyncore.loop()