BLIP/BLIPRequest.m
author Jens Alfke <jens@mooseyard.com>
Fri Jul 24 14:06:28 2009 -0700 (2009-07-24)
changeset 63 5e4855a592ee
parent 50 63baa74c903f
permissions -rw-r--r--
* The BLIPConnection receivedRequest: delegate method now returns BOOL. If the method returns NO (or if the method isn't implemented in the delegate), that means it didn't handle the message at all; an error will be returned to the sender.
* If the connection closes unexpectedly due to an error, then the auto-generated responses to pending requests will contain that error. This makes it easier to display a meaningful error message in the handler for the request.
     1 //
     2 //  BLIPRequest.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 5/22/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "BLIPRequest.h"
    10 #import "BLIP_Internal.h"
    11 #import "BLIPWriter.h"
    12 #import "BLIPReader.h"
    13 
    14 #import "Target.h"
    15 #import "Logging.h"
    16 #import "Test.h"
    17 #import "ExceptionUtils.h"
    18 
    19 
    20 @implementation BLIPRequest
    21 
    22 
    23 - (id) _initWithConnection: (BLIPConnection*)connection
    24                       body: (NSData*)body 
    25                 properties: (NSDictionary*)properties
    26 {
    27     self = [self _initWithConnection: connection
    28                               isMine: YES
    29                                flags: kBLIP_MSG
    30                               number: 0
    31                                 body: body];
    32     if( self ) {
    33         _isMutable = YES;
    34         if( body )
    35             self.body = body;
    36         if( properties )
    37             [self.mutableProperties setAllProperties: properties];
    38     }
    39     return self;
    40 }
    41 
    42 + (BLIPRequest*) requestWithBody: (NSData*)body
    43 {
    44     return [[[self alloc] _initWithConnection: nil body: body properties: nil] autorelease];
    45 }
    46 
    47 + (BLIPRequest*) requestWithBodyString: (NSString*)bodyString {
    48     return [self requestWithBody: [bodyString dataUsingEncoding: NSUTF8StringEncoding]];
    49 }
    50 
    51 + (BLIPRequest*) requestWithBody: (NSData*)body
    52                       properties: (NSDictionary*)properties
    53 {
    54     return [[[self alloc] _initWithConnection: nil body: body properties: properties] autorelease];
    55 }
    56 
    57 - (id)mutableCopyWithZone:(NSZone *)zone
    58 {
    59     Assert(self.complete);
    60     BLIPRequest *copy = [[self class] requestWithBody: self.body 
    61                                            properties: self.properties.allProperties];
    62     copy.compressed = self.compressed;
    63     copy.urgent = self.urgent;
    64     copy.noReply = self.noReply;
    65     return [copy retain];
    66 }
    67 
    68 
    69 - (void) dealloc
    70 {
    71     [_response release];
    72     [super dealloc];
    73 }
    74 
    75 
    76 - (BOOL) noReply                            {return (_flags & kBLIP_NoReply) != 0;}
    77 - (void) setNoReply: (BOOL)noReply          {[self _setFlag: kBLIP_NoReply value: noReply];}
    78 - (BLIPConnection*) connection              {return _connection;}
    79 
    80 - (void) setConnection: (BLIPConnection*)conn
    81 {
    82     Assert(_isMine && !_sent,@"Connection can only be set before sending");
    83     setObj(&_connection,conn);
    84 }
    85 
    86 
    87 - (BLIPResponse*) send
    88 {
    89     Assert(_connection,@"%@ has no connection to send over",self);
    90     Assert(!_sent,@"%@ was already sent",self);
    91     [self _encode];
    92     BLIPResponse *response = self.response;
    93     if( [(BLIPWriter*)_connection.writer sendRequest: self response: response] )
    94         self.sent = YES;
    95     else
    96         response = nil;
    97     return response;
    98 }
    99 
   100 
   101 - (BLIPResponse*) response
   102 {
   103     if( ! _response && ! self.noReply )
   104         _response = [[BLIPResponse alloc] _initWithRequest: self];
   105     return _response;
   106 }
   107 
   108 - (void) deferResponse
   109 {
   110     // This will allocate _response, causing -repliedTo to become YES, so BLIPConnection won't
   111     // send an automatic empty response after the current request handler returns.
   112     LogTo(BLIP,@"Deferring response to %@",self);
   113     [self response];
   114 }
   115 
   116 - (BOOL) repliedTo
   117 {
   118     return _response != nil;
   119 }
   120 
   121 - (void) respondWithData: (NSData*)data contentType: (NSString*)contentType
   122 {
   123     BLIPResponse *response = self.response;
   124     response.body = data;
   125     response.contentType = contentType;
   126     [response send];
   127 }
   128 
   129 - (void) respondWithString: (NSString*)string
   130 {
   131     [self respondWithData: [string dataUsingEncoding: NSUTF8StringEncoding]
   132               contentType: @"text/plain; charset=UTF-8"];
   133 }
   134 
   135 - (void) respondWithError: (NSError*)error
   136 {
   137     self.response.error = error; 
   138     [self.response send];
   139 }
   140 
   141 - (void) respondWithErrorCode: (int)errorCode message: (NSString*)errorMessage
   142 {
   143     [self respondWithError: BLIPMakeError(errorCode, @"%@",errorMessage)];
   144 }
   145 
   146 - (void) respondWithException: (NSException*)exception
   147 {
   148     [self respondWithError: BLIPMakeError(kBLIPError_HandlerFailed, @"%@", exception.reason)];
   149 }
   150 
   151 
   152 @end
   153 
   154 
   155 
   156 
   157 #pragma mark -
   158 @implementation BLIPResponse
   159 
   160 - (id) _initWithRequest: (BLIPRequest*)request
   161 {
   162     Assert(request);
   163     self = [super _initWithConnection: request.connection
   164                                isMine: !request.isMine
   165                                 flags: kBLIP_RPY | kBLIP_MoreComing
   166                                number: request.number
   167                                  body: nil];
   168     if (self != nil) {
   169         if( _isMine ) {
   170             _isMutable = YES;
   171             if( request.urgent )
   172                 _flags |= kBLIP_Urgent;
   173         } else {
   174             _flags |= kBLIP_MoreComing;
   175         }
   176     }
   177     return self;
   178 }
   179 
   180 - (void) dealloc
   181 {
   182     [_error release];
   183     [_onComplete release];
   184     [super dealloc];
   185 }
   186 
   187 
   188 - (NSError*) error
   189 {
   190     if( ! (_flags & kBLIP_ERR) )
   191         return nil;
   192     
   193     NSMutableDictionary *userInfo = [[[self.properties allProperties] mutableCopy] autorelease];
   194     NSString *domain = [userInfo objectForKey: @"Error-Domain"];
   195     int code = [[userInfo objectForKey: @"Error-Code"] intValue];
   196     if( domain==nil || code==0 ) {
   197         domain = BLIPErrorDomain;
   198         if( code==0 )
   199             code = kBLIPError_Unspecified;
   200     }
   201     [userInfo removeObjectForKey: @"Error-Domain"];
   202     [userInfo removeObjectForKey: @"Error-Code"];
   203     return [NSError errorWithDomain: domain code: code userInfo: userInfo];
   204 }
   205 
   206 - (void) _setError: (NSError*)error
   207 {
   208     _flags &= ~kBLIP_TypeMask;
   209     if( error ) {
   210         // Setting this stuff is a PITA because this object might be technically immutable,
   211         // in which case the standard setters would barf if I called them.
   212         _flags |= kBLIP_ERR;
   213         setObj(&_body,nil);
   214         setObj(&_mutableBody,nil);
   215         
   216         BLIPMutableProperties *errorProps = [self.properties mutableCopy];
   217         if( ! errorProps )
   218             errorProps = [[BLIPMutableProperties alloc] init];
   219         NSDictionary *userInfo = error.userInfo;
   220         for( NSString *key in userInfo ) {
   221             id value = $castIf(NSString,[userInfo objectForKey: key]);
   222             if( value )
   223                 [errorProps setValue: value ofProperty: key];
   224         }
   225         [errorProps setValue: error.domain ofProperty: @"Error-Domain"];
   226         [errorProps setValue: $sprintf(@"%i",error.code) ofProperty: @"Error-Code"];
   227         setObj(&_properties,errorProps);
   228         [errorProps release];
   229         
   230     } else {
   231         _flags |= kBLIP_RPY;
   232         [self.mutableProperties setAllProperties: nil];
   233     }
   234 }
   235 
   236 - (void) setError: (NSError*)error
   237 {
   238     Assert(_isMine && _isMutable);
   239     [self _setError: error];
   240 }
   241 
   242 
   243 - (BOOL) send
   244 {
   245     Assert(_connection,@"%@ has no connection to send over",self);
   246     Assert(!_sent,@"%@ was already sent",self);
   247     BLIPWriter *writer = (BLIPWriter*)_connection.writer;
   248     Assert(writer,@"%@'s connection has no writer (already closed?)",self);
   249     [self _encode];
   250     BOOL sent = self.sent = [writer sendMessage: self];
   251     Assert(sent);
   252     return sent;
   253 }
   254 
   255 
   256 @synthesize onComplete=_onComplete;
   257 
   258 
   259 - (void) setComplete: (BOOL)complete
   260 {
   261     [super setComplete: complete];
   262     if( complete && _onComplete ) {
   263         @try{
   264             [_onComplete invokeWithSender: self];
   265         }catchAndReport(@"BLIPRequest onComplete target");
   266     }
   267 }
   268 
   269 
   270 - (void) _connectionClosed
   271 {
   272     [super _connectionClosed];
   273     if( !_isMine && !_complete ) {
   274         NSError *error = _connection.error;
   275         if (!error)
   276             error = BLIPMakeError(kBLIPError_Disconnected,
   277                                   @"Connection closed before response was received");
   278         // Change incoming response to an error:
   279         _isMutable = YES;
   280         [_properties autorelease];
   281         _properties = [_properties mutableCopy];
   282         [self _setError: error];
   283         _isMutable = NO;
   284         
   285         self.complete = YES;    // Calls onComplete target
   286     }
   287 }
   288 
   289 
   290 @end
   291 
   292 
   293 /*
   294  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   295  
   296  Redistribution and use in source and binary forms, with or without modification, are permitted
   297  provided that the following conditions are met:
   298  
   299  * Redistributions of source code must retain the above copyright notice, this list of conditions
   300  and the following disclaimer.
   301  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   302  and the following disclaimer in the documentation and/or other materials provided with the
   303  distribution.
   304  
   305  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   306  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   307  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   308  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   309  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   310  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   311  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   312  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   313  */