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