| /* |
| * 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 |