blob: 5d62fe95e07ca9a25f0dfb2a8631d4741c7c143a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "UGHTTPManager.h"
// all transaction IDs are unique across all UGHTTPManagers.
// this global increases every time there's an asynchronous
// transaction.
static int g_nextTransactionID = 1;
// a mutex to protect against multiple threads getting
// IDs at the same time, and possibly getting the same ID
// because of it
NSRecursiveLock *g_transactionIDLock = nil;
@implementation UGHTTPManager
{
// data management for collecting incoming HTTP data
// during asynch transactions
NSMutableData *m_receivedData;
// a general error string
NSString *m_lastError;
// the delegate sent in to the asynch method
id m_delegate;
// the transaction ID of the current (or most recent) transaction
int m_transactionID;
// availability of this instance. Managed by UGClient
BOOL m_bAvailable;
// mutex used to ensure the delegate is not changed
// in a way that would cause a race condition.
NSRecursiveLock *m_delegateLock;
// the auth key to send along to requests
NSString *m_auth;
}
-(id)init
{
self = [super init];
if ( self )
{
m_lastError = @"No error";
m_receivedData = [NSMutableData data];
m_bAvailable = YES;
m_delegateLock = [NSRecursiveLock new];
m_transactionID = -1;
m_auth = nil;
// lazy-init the transaction lock
if ( !g_transactionIDLock )
{
g_transactionIDLock = [NSRecursiveLock new];
}
}
return self;
}
-(void)setAuth: (NSString *)auth
{
m_auth = auth;
}
-(NSString *)getLastError
{
return m_lastError;
}
-(int)getNextTransactionID
{
// make sure we can use this lock
assert(g_transactionIDLock);
[g_transactionIDLock lock];
int ret = g_nextTransactionID++;
[g_transactionIDLock unlock];
return ret;
}
-(int)getTransactionID
{
return m_transactionID;
}
//----------------------- SYNCHRONOUS CALLING ------------------------
-(NSString *)syncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData;
{
// clear the transaction ID
m_transactionID = -1;
// use the synchronous funcitonality of NSURLConnection
// clear the error
m_lastError = @"No error";
// formulate the request
NSURLRequest *req = [self getRequest:url operation:op operationData:opData];
NSURLResponse *response;
NSError *error;
NSData *resultData = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
if ( resultData )
{
// we got results
NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
return resultString;
}
// if we're here, it means we got nil as the result
m_lastError = [error localizedDescription];
return nil;
}
//----------------------- ASYNCHRONOUS CALLING ------------------------
-(int)asyncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData delegate:(id)delegate;
{
// clear the transaction ID
m_transactionID = -1;
// clear the error
m_lastError = @"No error";
if ( !delegate )
{
// an asynch transaction with no delegate has no meaning
m_lastError = @"Delegate was nil";
return -1;
}
// make sure the delegate responds to the various messages that
// are required
if ( ![delegate respondsToSelector:@selector(httpManagerError:error:)] )
{
m_lastError = @"Delegate does not have httpManagerError:error: method";
return -1;
}
if ( ![delegate respondsToSelector:@selector(httpManagerResponse:response:)] )
{
m_lastError = @"Delegate does not have httpManagerResponse:response: method";
return -1;
}
// only once we're assured everything is right do we set the internal value
[m_delegateLock lock];
m_delegate = delegate;
[m_delegateLock unlock];
// prep a transaction ID for this transaction
m_transactionID = [self getNextTransactionID];
// formulate the request
NSURLRequest *req = [self getRequest:url operation:op operationData:opData];
// fire it off
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
// Note failure
if ( !conn )
{
// failed to connect
m_lastError = @"Unable to initiate connection.";
return -1;
}
// success
return m_transactionID;
}
-(BOOL)isAvailable
{
return m_bAvailable;
}
-(void)setAvailable:(BOOL)available
{
m_bAvailable = available;
}
-(void)cancel
{
// we wrap this in a lock to ensure that the client can
// call it at any time. It will not cause a race condition in
// any callback thread.
[m_delegateLock lock];
m_delegate = nil;
[m_delegateLock unlock];
// note that we do not modify the "in use" flag. If we werei n use,
// we remain in use until we receive a response or error. This ensures
// no confusion on any subsequent transaction. We don't want the case
// where a transaction is started, then cancelled, then a new transaction begun
// before the first transaction's result comes in. That would lead to the second
// transaction being answered with the first's reply. We avoid that possibility by
// simply remaining "in use" until we get the reply or error.
}
// general helper function form aking escaped-strings
+(NSString *)escapeSpecials:(NSString *)raw;
{
NSString *converted = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)raw, nil, CFSTR(";/?:@&=$+{}<>"), kCFStringEncodingUTF8);
return converted;
}
// INTERNAL function for forming the request
-(NSURLRequest *)getRequest:(NSString *)url operation:(int)op operationData:(NSString *)opStr;
{
// make the url
NSURL *nsurl = [NSURL URLWithString:url];
NSMutableURLRequest *req = [NSMutableURLRequest new];
[req setURL:nsurl];
switch ( op )
{
case kUGHTTPGet: [req setHTTPMethod:@"GET"]; break;
case kUGHTTPPost: [req setHTTPMethod:@"POST"]; break;
case kUGHTTPPostAuth: [req setHTTPMethod:@"POST"]; break;
case kUGHTTPPut: [req setHTTPMethod:@"PUT"]; break;
case kUGHTTPDelete: [req setHTTPMethod:@"DELETE"]; break;
}
// set the auth, if any is available
if ( m_auth )
{
NSMutableString *authStr = [NSMutableString new];
[authStr appendFormat:@"Bearer %@", m_auth];
[req setValue:authStr forHTTPHeaderField:@"Authorization"];
}
// if they sent an opStr, we make that the content
if ( opStr )
{
// prep the post data
NSData *opData = [opStr dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
// make a string that tells the length of the post data. We'll need that for the HTTP header setup
NSString *opLength = [NSString stringWithFormat:@"%d", [opData length]];
[req setValue:opLength forHTTPHeaderField:@"Content-Length"];
// PostAuth uses form encoding. All other operations use json
if ( op == kUGHTTPPostAuth )
{
[req setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
else
{
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
[req setHTTPBody:opData];
}
// all set up and ready for use
return req;
}
//------------------------------------------------------------------------------------------
//-------------------------- NSURLCONNECTION DELEGATE METHODS ------------------------------
//------------------------------------------------------------------------------------------
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// got a response. clear out the data
[m_receivedData setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// got some data. Append it.
[m_receivedData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// connection failed. Note the error
m_lastError = [error localizedDescription];
// send the error to the delegate. We wrap this in
// send the result to the delegate.
// wrap this in mutex locks, then check for validity of m_delegate inside.
// this ensures no race conditions and allows arbitrary cancellation of callbacks
[m_delegateLock lock];
if ( m_delegate )
{
[m_delegate performSelector:@selector(httpManagerError:error:) withObject:self withObject:m_lastError];
}
m_delegate = nil;
[m_delegateLock unlock];
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
// all done. Let's turn it in to a string
NSString *resultString = [[NSString alloc] initWithData:m_receivedData encoding:NSUTF8StringEncoding];
// send it to the delegate. See connection:didFailWithError: for an explanation
// of the mutex locks
[m_delegateLock lock];
if ( m_delegate )
{
[m_delegate performSelector:@selector(httpManagerResponse:response:) withObject:self withObject:resultString];
}
m_delegate = nil;
[m_delegateLock unlock];
}
@end