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