246 lines
12 KiB
C
246 lines
12 KiB
C
|
|
/*
|
||
|
|
File: QHTTPOperation.h
|
||
|
|
|
||
|
|
Contains: An NSOperation that runs an HTTP request.
|
||
|
|
|
||
|
|
Written by: DTS
|
||
|
|
|
||
|
|
Copyright: Copyright (c) 2010 Apple Inc. All Rights Reserved.
|
||
|
|
|
||
|
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
|
||
|
|
("Apple") in consideration of your agreement to the following
|
||
|
|
terms, and your use, installation, modification or
|
||
|
|
redistribution of this Apple software constitutes acceptance of
|
||
|
|
these terms. If you do not agree with these terms, please do
|
||
|
|
not use, install, modify or redistribute this Apple software.
|
||
|
|
|
||
|
|
In consideration of your agreement to abide by the following
|
||
|
|
terms, and subject to these terms, Apple grants you a personal,
|
||
|
|
non-exclusive license, under Apple's copyrights in this
|
||
|
|
original Apple software (the "Apple Software"), to use,
|
||
|
|
reproduce, modify and redistribute the Apple Software, with or
|
||
|
|
without modifications, in source and/or binary forms; provided
|
||
|
|
that if you redistribute the Apple Software in its entirety and
|
||
|
|
without modifications, you must retain this notice and the
|
||
|
|
following text and disclaimers in all such redistributions of
|
||
|
|
the Apple Software. Neither the name, trademarks, service marks
|
||
|
|
or logos of Apple Inc. may be used to endorse or promote
|
||
|
|
products derived from the Apple Software without specific prior
|
||
|
|
written permission from Apple. Except as expressly stated in
|
||
|
|
this notice, no other rights or licenses, express or implied,
|
||
|
|
are granted by Apple herein, including but not limited to any
|
||
|
|
patent rights that may be infringed by your derivative works or
|
||
|
|
by other works in which the Apple Software may be incorporated.
|
||
|
|
|
||
|
|
The Apple Software is provided by Apple on an "AS IS" basis.
|
||
|
|
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
||
|
|
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
|
||
|
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
|
||
|
|
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
||
|
|
COMBINATION WITH YOUR PRODUCTS.
|
||
|
|
|
||
|
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
|
||
|
|
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
|
||
|
|
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
|
||
|
|
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
|
||
|
|
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
|
||
|
|
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||
|
|
SUCH DAMAGE.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
#import "QRunLoopOperation.h"
|
||
|
|
|
||
|
|
/*
|
||
|
|
QHTTPOperation is a general purpose NSOperation that runs an HTTP request.
|
||
|
|
You initialise it with an HTTP request and then, when you run the operation,
|
||
|
|
it sends the request and gathers the response. It is quite a complex
|
||
|
|
object because it handles a wide variety of edge cases, but it's very
|
||
|
|
easy to use in simple cases:
|
||
|
|
|
||
|
|
1. create the operation with the URL you want to get
|
||
|
|
|
||
|
|
op = [[[QHTTPOperation alloc] initWithURL:url] autorelease];
|
||
|
|
|
||
|
|
2. set up any non-default parameters, for example, set which HTTP
|
||
|
|
content types are acceptable
|
||
|
|
|
||
|
|
op.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
|
||
|
|
|
||
|
|
3. enqueue the operation
|
||
|
|
|
||
|
|
[queue addOperation:op];
|
||
|
|
|
||
|
|
4. finally, when the operation is done, use the lastResponse and
|
||
|
|
error properties to find out how things went
|
||
|
|
|
||
|
|
As mentioned above, QHTTPOperation is very general purpose. There are a
|
||
|
|
large number of configuration and result options available to you.
|
||
|
|
|
||
|
|
o You can specify a NSURLRequest rather than just a URL.
|
||
|
|
|
||
|
|
o You can configure the run loop and modes on which the NSURLConnection is
|
||
|
|
scheduled.
|
||
|
|
|
||
|
|
o You can specify what HTTP status codes and content types are OK.
|
||
|
|
|
||
|
|
o You can set an authentication delegate to handle authentication challenges.
|
||
|
|
|
||
|
|
o You can accumulate responses in memory or in an NSOutputStream.
|
||
|
|
|
||
|
|
o For in-memory responses, you can specify a default response size
|
||
|
|
(used to size the response buffer) and a maximum response size
|
||
|
|
(to prevent unbounded memory use).
|
||
|
|
|
||
|
|
o You can get at the last request and the last response, to track
|
||
|
|
redirects.
|
||
|
|
|
||
|
|
o There are a variety of funky debugging options to simulator errors
|
||
|
|
and delays.
|
||
|
|
|
||
|
|
Finally, it's perfectly reasonable to subclass QHTTPOperation to meet you
|
||
|
|
own specific needs. Specifically, it's common for the subclass to
|
||
|
|
override -connection:didReceiveResponse: in order to setup the output
|
||
|
|
stream based on the specific details of the response.
|
||
|
|
*/
|
||
|
|
|
||
|
|
@protocol QHTTPOperationAuthenticationDelegate;
|
||
|
|
|
||
|
|
@interface QHTTPOperation : QRunLoopOperation /* <NSURLConnectionDelegate> */
|
||
|
|
{
|
||
|
|
NSURLRequest * _request;
|
||
|
|
NSIndexSet * _acceptableStatusCodes;
|
||
|
|
NSSet * _acceptableContentTypes;
|
||
|
|
id<QHTTPOperationAuthenticationDelegate> _authenticationDelegate;
|
||
|
|
NSOutputStream * _responseOutputStream;
|
||
|
|
NSUInteger _defaultResponseSize;
|
||
|
|
NSUInteger _maximumResponseSize;
|
||
|
|
NSURLConnection * _connection;
|
||
|
|
BOOL _firstData;
|
||
|
|
NSMutableData * _dataAccumulator;
|
||
|
|
NSURLRequest * _lastRequest;
|
||
|
|
NSHTTPURLResponse * _lastResponse;
|
||
|
|
NSData * _responseBody;
|
||
|
|
#if ! defined(NDEBUG)
|
||
|
|
NSError * _debugError;
|
||
|
|
NSTimeInterval _debugDelay;
|
||
|
|
NSTimer * _debugDelayTimer;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
- (id)initWithRequest:(NSURLRequest *)request; // designated
|
||
|
|
- (id)initWithURL:(NSURL *)url; // convenience, calls +[NSURLRequest requestWithURL:]
|
||
|
|
|
||
|
|
// Things that are configured by the init method and can't be changed.
|
||
|
|
|
||
|
|
@property (copy, readonly) NSURLRequest * request;
|
||
|
|
@property (copy, readonly) NSURL * URL;
|
||
|
|
|
||
|
|
// Things you can configure before queuing the operation.
|
||
|
|
|
||
|
|
// runLoopThread and runLoopModes inherited from QRunLoopOperation
|
||
|
|
@property (copy, readwrite) NSIndexSet * acceptableStatusCodes; // default is nil, implying 200..299
|
||
|
|
@property (copy, readwrite) NSSet * acceptableContentTypes; // default is nil, implying anything is acceptable
|
||
|
|
@property (assign, readwrite) id<QHTTPOperationAuthenticationDelegate> authenticationDelegate;
|
||
|
|
|
||
|
|
#if ! defined(NDEBUG)
|
||
|
|
@property (copy, readwrite) NSError * debugError; // default is nil
|
||
|
|
@property (assign, readwrite) NSTimeInterval debugDelay; // default is none
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// Things you can configure up to the point where you start receiving data.
|
||
|
|
// Typically you would change these in -connection:didReceiveResponse:, but
|
||
|
|
// it is possible to change them up to the point where -connection:didReceiveData:
|
||
|
|
// is called for the first time (that is, you could override -connection:didReceiveData:
|
||
|
|
// and change these before calling super).
|
||
|
|
|
||
|
|
// IMPORTANT: If you set a response stream, QHTTPOperation calls the response
|
||
|
|
// stream synchronously. This is fine for file and memory streams, but it would
|
||
|
|
// not work well for other types of streams (like a bound pair).
|
||
|
|
|
||
|
|
@property (retain, readwrite) NSOutputStream * responseOutputStream; // defaults to nil, which puts response into responseBody
|
||
|
|
@property (assign, readwrite) NSUInteger defaultResponseSize; // default is 1 MB, ignored if responseOutputStream is set
|
||
|
|
@property (assign, readwrite) NSUInteger maximumResponseSize; // default is 4 MB, ignored if responseOutputStream is set
|
||
|
|
// defaults are 1/4 of the above on embedded
|
||
|
|
|
||
|
|
// Things that are only meaningful after a response has been received;
|
||
|
|
|
||
|
|
@property (assign, readonly, getter=isStatusCodeAcceptable) BOOL statusCodeAcceptable;
|
||
|
|
@property (assign, readonly, getter=isContentTypeAcceptable) BOOL contentTypeAcceptable;
|
||
|
|
|
||
|
|
// Things that are only meaningful after the operation is finished.
|
||
|
|
|
||
|
|
// error property inherited from QRunLoopOperation
|
||
|
|
@property (copy, readonly) NSURLRequest * lastRequest;
|
||
|
|
@property (copy, readonly) NSHTTPURLResponse * lastResponse;
|
||
|
|
|
||
|
|
@property (copy, readonly) NSData * responseBody;
|
||
|
|
|
||
|
|
@end
|
||
|
|
|
||
|
|
@interface QHTTPOperation (NSURLConnectionDelegate)
|
||
|
|
|
||
|
|
// QHTTPOperation implements all of these methods, so if you override them
|
||
|
|
// you must consider whether or not to call super.
|
||
|
|
//
|
||
|
|
// These will be called on the operation's run loop thread.
|
||
|
|
|
||
|
|
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;
|
||
|
|
// Routes the request to the authentication delegate if it exists, otherwise
|
||
|
|
// just returns NO.
|
||
|
|
|
||
|
|
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
|
||
|
|
// Routes the request to the authentication delegate if it exists, otherwise
|
||
|
|
// just cancels the challenge.
|
||
|
|
|
||
|
|
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response;
|
||
|
|
// Latches the request and response in lastRequest and lastResponse.
|
||
|
|
|
||
|
|
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
|
||
|
|
// Latches the response in lastResponse.
|
||
|
|
|
||
|
|
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
|
||
|
|
// If this is the first chunk of data, it decides whether the data is going to be
|
||
|
|
// routed to memory (responseBody) or a stream (responseOutputStream) and makes the
|
||
|
|
// appropriate preparations. For this and subsequent data it then actually shuffles
|
||
|
|
// the data to its destination.
|
||
|
|
|
||
|
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
|
||
|
|
// Completes the operation with either no error (if the response status code is acceptable)
|
||
|
|
// or an error (otherwise).
|
||
|
|
|
||
|
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
|
||
|
|
// Completes the operation with the error.
|
||
|
|
|
||
|
|
@end
|
||
|
|
|
||
|
|
@protocol QHTTPOperationAuthenticationDelegate <NSObject>
|
||
|
|
@required
|
||
|
|
|
||
|
|
// These are called on the operation's run loop thread and have the same semantics as their
|
||
|
|
// NSURLConnection equivalents. It's important to realise that there is no
|
||
|
|
// didCancelAuthenticationChallenge callback (because NSURLConnection doesn't issue one to us).
|
||
|
|
// Rather, an authentication delegate is expected to observe the operation and cancel itself
|
||
|
|
// if the operation completes while the challenge is running.
|
||
|
|
|
||
|
|
- (BOOL)httpOperation:(QHTTPOperation *)operation canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;
|
||
|
|
- (void)httpOperation:(QHTTPOperation *)operation didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
|
||
|
|
|
||
|
|
@end
|
||
|
|
|
||
|
|
extern NSString * kQHTTPOperationErrorDomain;
|
||
|
|
|
||
|
|
// positive error codes are HTML status codes (when they are not allowed via acceptableStatusCodes)
|
||
|
|
//
|
||
|
|
// 0 is, of course, not a valid error code
|
||
|
|
//
|
||
|
|
// negative error codes are errors from the module
|
||
|
|
|
||
|
|
enum {
|
||
|
|
kQHTTPOperationErrorResponseTooLarge = -1,
|
||
|
|
kQHTTPOperationErrorOnOutputStream = -2,
|
||
|
|
kQHTTPOperationErrorBadContentType = -3
|
||
|
|
};
|