Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
509 lines (451 sloc) 21 KB
// GTMHTTPFetcher.h
// Copyright 2007-2008 Google Inc.
// Licensed 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
// 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.
// This is essentially a wrapper around NSURLConnection for POSTs and GETs.
// If setPostData: is called, then POST is assumed.
// When would you use this instead of NSURLConnection?
// - When you just want the result from a GET or POST
// - When you want the "standard" behavior for connections (redirection handling
// an so on)
// - When you want to avoid cookie collisions with Safari and other applications
// - When you want to provide if-modified-since headers
// - When you need to set a credential for the http
// - When you want to avoid changing WebKit's cookies
// This is assumed to be a one-shot fetch request; don't reuse the object
// for a second fetch.
// The fetcher may be created auto-released, in which case it will release
// itself after the fetch completion callback. The fetcher
// is implicitly retained as long as a connection is pending.
// But if you may need to cancel the fetcher, allocate it with initWithRequest:
// and have the delegate release the fetcher in the callbacks.
// Sample usage:
// NSURLRequest *request = [NSURLRequest requestWithURL:myURL];
// GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher httpFetcherWithRequest:request];
// [myFetcher setPostData:[postString dataUsingEncoding:NSUTF8StringEncoding]]; // for POSTs
// [myFetcher setCredential:[NSURLCredential authCredentialWithUsername:@"foo"
// password:@"bar"]]; // optional http credential
// [myFetcher setFetchHistory:myMutableDictionary]; // optional, for persisting modified-dates
// [myFetcher beginFetchWithDelegate:self
// didFinishSelector:@selector(myFetcher:finishedWithData:)
// didFailSelector:@selector(myFetcher:failedWithError:)];
// Upon fetch completion, the callback selectors are invoked; they should have
// these signatures (you can use any callback method names you want so long as
// the signatures match these):
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)retrievedData;
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error;
// NOTE: Fetches may retrieve data from the server even though the server
// returned an error. The failWithError selector is called when the server
// status is >= 300 (along with any server-supplied data, usually
// some html explaining the error).
// Status codes are at <>
// Proxies:
// Proxy handling is invisible so long as the system has a valid credential in
// the keychain, which is normally true (else most NSURL-based apps would have
// difficulty.) But when there is a proxy authetication error, the the fetcher
// will call the failedWithError: method with the NSURLChallenge in the error's
// userInfo. The error method can get the challenge info like this:
// NSURLAuthenticationChallenge *challenge
// = [[error userInfo] objectForKey:kGTMHTTPFetcherErrorChallengeKey];
// BOOL isProxyChallenge = [[challenge protectionSpace] isProxy];
// If a proxy error occurs, you can ask the user for the proxy username/password
// and call fetcher's setProxyCredential: to provide those for the
// next attempt to fetch.
// Cookies:
// There are three supported mechanisms for remembering cookies between fetches.
// By default, GTMHTTPFetcher uses a mutable array held statically to track
// cookies for all instantiated fetchers. This avoids server cookies being set
// by servers for the application from interfering with Safari cookie settings,
// and vice versa. The fetcher cookies are lost when the application quits.
// To rely instead on WebKit's global NSHTTPCookieStorage, call
// setCookieStorageMethod: with kGTMHTTPFetcherCookieStorageMethodSystemDefault.
// If you provide a fetch history (such as for periodic checks, described
// below) then the cookie storage mechanism is set to use the fetch
// history rather than the static storage.
// Fetching for periodic checks:
// The fetcher object can track "Last-modified" dates on returned data and
// provide an "If-modified-since" header. This allows the server to save
// bandwidth by providing a "Nothing changed" status message instead of response
// data.
// To get this behavior, provide a persistent mutable dictionary to setFetchHistory:,
// and look for the failedWithError: callback with code 304
// (kGTMHTTPFetcherStatusNotModified) like this:
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error {
// if ([[error domain] isEqual:kGTMHTTPFetcherStatusDomain] &&
// ([error code] == kGTMHTTPFetcherStatusNotModified)) {
// // [[error userInfo] objectForKey:kGTMHTTPFetcherStatusDataKey] is
// // empty; use the data from the previous finishedWithData: for this URL
// } else {
// // handle other server status code
// }
// }
// The fetchHistory mutable dictionary should be maintained by the client between
// fetches and given to each fetcher intended to have the If-modified-since header
// or the same cookie storage.
// Monitoring received data
// The optional received data selector should have the signature
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher receivedData:(NSData *)dataReceivedSoFar;
// The bytes received so far are [dataReceivedSoFar length]. This number may go down
// if a redirect causes the download to begin again from a new server.
// If supplied by the server, the anticipated total download size is available as
// [[myFetcher response] expectedContentLength] (may be -1 for unknown
// download sizes.)
// Automatic retrying of fetches
// The fetcher can optionally create a timer and reattempt certain kinds of
// fetch failures (status codes 408, request timeout; 503, service unavailable;
// 504, gateway timeout; networking errors NSURLErrorTimedOut and
// NSURLErrorNetworkConnectionLost.) The user may set a retry selector to
// customize the type of errors which will be retried.
// Retries are done in an exponential-backoff fashion (that is, after 1 second,
// 2, 4, 8, and so on.)
// Enabling automatic retries looks like this:
// [myFetcher setIsRetryEnabled:YES];
// With retries enabled, the success or failure callbacks are called only
// when no more retries will be attempted. Calling the fetcher's stopFetching
// method will terminate the retry timer, without the finished or failure
// selectors being invoked.
// Optionally, the client may set the maximum retry interval:
// [myFetcher setMaxRetryInterval:60.]; // in seconds; default is 600 seconds
// Also optionally, the client may provide a callback selector to determine
// if a status code or other error should be retried.
// [myFetcher setRetrySelector:@selector(myFetcher:willRetry:forError:)];
// If set, the retry selector should have the signature:
// -(BOOL)fetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error
// and return YES to set the retry timer or NO to fail without additional
// fetch attempts.
// The retry method may return the |suggestedWillRetry| argument to get the
// default retry behavior. Server status codes are present in the error
// argument, and have the domain kGTMHTTPFetcherStatusDomain. The user's method
// may look something like this:
// -(BOOL)myFetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error {
// // perhaps examine [error domain] and [error code], or [fetcher retryCount]
// //
// // return YES to start the retry timer, NO to proceed to the failure
// // callback, or |suggestedWillRetry| to get default behavior for the
// // current error domain and code values.
// return suggestedWillRetry;
// }
#pragma once
#import <Foundation/Foundation.h>
#import "GTMDefines.h"
#undef _EXTERN
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#define _INITIALIZE_AS(x)
// notifications & errors
_EXTERN NSString* const kGTMHTTPFetcherErrorDomain _INITIALIZE_AS(@"");
_EXTERN NSString* const kGTMHTTPFetcherStatusDomain _INITIALIZE_AS(@"");
_EXTERN NSString* const kGTMHTTPFetcherErrorChallengeKey _INITIALIZE_AS(@"challenge");
_EXTERN NSString* const kGTMHTTPFetcherStatusDataKey _INITIALIZE_AS(@"data"); // any data returns w/ a kGTMHTTPFetcherStatusDomain error
// fetch history mutable dictionary keys
_EXTERN NSString* const kGTMHTTPFetcherHistoryLastModifiedKey _INITIALIZE_AS(@"FetchHistoryLastModified");
_EXTERN NSString* const kGTMHTTPFetcherHistoryDatedDataKey _INITIALIZE_AS(@"FetchHistoryDatedDataCache");
_EXTERN NSString* const kGTMHTTPFetcherHistoryCookiesKey _INITIALIZE_AS(@"FetchHistoryCookies");
enum {
kGTMHTTPFetcherErrorDownloadFailed = -1,
kGTMHTTPFetcherErrorAuthenticationChallengeFailed = -2,
kGTMHTTPFetcherStatusNotModified = 304
enum {
kGTMHTTPFetcherCookieStorageMethodStatic = 0,
kGTMHTTPFetcherCookieStorageMethodFetchHistory = 1,
kGTMHTTPFetcherCookieStorageMethodSystemDefault = 2
typedef NSUInteger GTMHTTPFetcherCookieStorageMethod;
/// async retrieval of an http get or post
@interface GTMHTTPFetcher : NSObject {
NSMutableURLRequest *request_;
NSURLConnection *connection_; // while connection_ is non-nil, delegate_ is retained
NSMutableData *downloadedData_;
NSURLCredential *credential_; // username & password
NSURLCredential *proxyCredential_; // credential supplied to proxy servers
NSData *postData_;
NSInputStream *postStream_;
NSMutableData *loggedStreamData_;
NSURLResponse *response_; // set in connection:didReceiveResponse:
id delegate_; // WEAK (though retained during an open connection)
SEL finishedSEL_; // should by implemented by delegate
SEL failedSEL_; // should be implemented by delegate
SEL receivedDataSEL_; // optional, set with setReceivedDataSelector
id userData_; // retained, if set by caller
NSMutableDictionary *properties_; // more data retained for caller
NSArray *runLoopModes_; // optional, for 10.5 and later
NSMutableDictionary *fetchHistory_; // if supplied by the caller, used for Last-Modified-Since checks and cookies
BOOL shouldCacheDatedData_; // if true, remembers and returns data marked with a last-modified date
GTMHTTPFetcherCookieStorageMethod cookieStorageMethod_; // constant from above
BOOL isRetryEnabled_; // user wants auto-retry
SEL retrySEL_; // optional; set with setRetrySelector
NSTimer *retryTimer_;
unsigned int retryCount_;
NSTimeInterval maxRetryInterval_; // default 600 seconds
NSTimeInterval minRetryInterval_; // random between 1 and 2 seconds
NSTimeInterval retryFactor_; // default interval multiplier is 2
NSTimeInterval lastRetryInterval_;
/// create a fetcher
// httpFetcherWithRequest will return an autoreleased fetcher, but if
// the connection is successfully created, the connection should retain the
// fetcher for the life of the connection as well. So the caller doesn't have
// to retain the fetcher explicitly unless they want to be able to cancel it.
+ (GTMHTTPFetcher *)httpFetcherWithRequest:(NSURLRequest *)request;
// designated initializer
- (id)initWithRequest:(NSURLRequest *)request;
- (NSMutableURLRequest *)request;
- (void)setRequest:(NSURLRequest *)theRequest;
// setting the credential is optional; it is used if the connection receives
// an authentication challenge
- (NSURLCredential *)credential;
- (void)setCredential:(NSURLCredential *)theCredential;
// setting the proxy credential is optional; it is used if the connection
// receives an authentication challenge from a proxy
- (NSURLCredential *)proxyCredential;
- (void)setProxyCredential:(NSURLCredential *)theCredential;
// if post data or stream is not set, then a GET retrieval method is assumed
- (NSData *)postData;
- (void)setPostData:(NSData *)theData;
// beware: In 10.4, NSInputStream fails to copy or retain
// the data it was initialized with, contrary to docs.
// 1, postStream will return a GTMProgressMonitorInputStream that wraps your
// stream (so the upload can be logged).
- (NSInputStream *)postStream;
- (void)setPostStream:(NSInputStream *)theStream;
- (GTMHTTPFetcherCookieStorageMethod)cookieStorageMethod;
- (void)setCookieStorageMethod:(GTMHTTPFetcherCookieStorageMethod)method;
// returns cookies from the currently appropriate cookie storage
- (NSArray *)cookiesForURL:(NSURL *)theURL;
// the delegate is not retained except during the connection
- (id)delegate;
- (void)setDelegate:(id)theDelegate;
// the delegate's optional receivedData selector has a signature like:
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher receivedData:(NSData *)dataReceivedSoFar;
- (SEL)receivedDataSelector;
- (void)setReceivedDataSelector:(SEL)theSelector;
// retrying; see comments at the top of the file. Calling
// setIsRetryEnabled(YES) resets the min and max retry intervals.
- (BOOL)isRetryEnabled;
- (void)setIsRetryEnabled:(BOOL)flag;
// retry selector is optional for retries.
// If present, it should have the signature:
// -(BOOL)fetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error
// and return YES to cause a retry. See comments at the top of this file.
- (SEL)retrySelector;
- (void)setRetrySelector:(SEL)theSel;
// retry intervals must be strictly less than maxRetryInterval, else
// they will be limited to maxRetryInterval and no further retries will
// be attempted. Setting maxRetryInterval to 0.0 will reset it to the
// default value, 600 seconds.
- (NSTimeInterval)maxRetryInterval;
- (void)setMaxRetryInterval:(NSTimeInterval)secs;
// Starting retry interval. Setting minRetryInterval to 0.0 will reset it
// to a random value between 1.0 and 2.0 seconds. Clients should normally not
// call this except for unit testing.
- (NSTimeInterval)minRetryInterval;
- (void)setMinRetryInterval:(NSTimeInterval)secs;
// Multiplier used to increase the interval between retries, typically 2.0.
// Clients should not need to call this.
- (double)retryFactor;
- (void)setRetryFactor:(double)multiplier;
// number of retries attempted
- (unsigned int)retryCount;
// interval delay to precede next retry
- (NSTimeInterval)nextRetryInterval;
/// Begin fetching the request.
/// |delegate| can optionally implement the two selectors |finishedSEL| and
/// |networkFailedSEL| or pass nil for them.
/// Returns YES if the fetch is initiated. Delegate is retained between
/// the beginFetch call until after the finish/fail callbacks.
// finishedSEL has a signature like:
// - (void)fetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data
// failedSEL has a signature like:
// - (void)fetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error
- (BOOL)beginFetchWithDelegate:(id)delegate
// Returns YES if this is in the process of fetching a URL
- (BOOL)isFetching;
/// Cancel the fetch of the request that's currently in progress
- (void)stopFetching;
/// return the status code from the server response
- (NSInteger)statusCode;
/// the response, once it's been received
- (NSURLResponse *)response;
- (void)setResponse:(NSURLResponse *)response;
// Fetch History is a useful, but a little complex at times...
// The caller should provide a mutable dictionary that can be used for storing
// Last-Modified-Since checks and cookie storage (setFetchHistory implicity
// calls setCookieStorageMethod w/
// kGTMHTTPFetcherCookieStorageMethodFetchHistory if you passed a dictionary,
// kGTMHTTPFetcherCookieStorageMethodStatic if you passed nil.
// The caller can hold onto the dictionary to reuse the modification dates and
// cookies across multiple fetcher instances.
// With a fetch history dictionary setup, the http fetcher cache has the
// modification dates returned by the servers cached, and future fetches will
// return 304 to indicate the data hasn't changed since then (the data in the
// NSError object will be of length zero to show nothing was fetched). This
// reduces load on the server when the response data has not changed. See
// shouldCacheDatedData below for additional 304 support.
// Side effect: setFetchHistory: implicitly calls setCookieStorageMethod:
- (NSMutableDictionary *)fetchHistory;
- (void)setFetchHistory:(NSMutableDictionary *)fetchHistory;
// For fetched data with a last-modified date, the fetcher can optionally cache
// the response data in the fetch history and return cached data instead of a
// 304 error. Set this to NO if you want to manually handle last-modified and
// status 304 (Not changed) rather than be delivered cached data from previous
// fetches. Default is NO. When a cache result is returned, the didFinish
// selector is called with the data, and [fetcher status] returns 200.
// If the caller has already provided a fetchHistory dictionary, they can also
// enable fetcher handling of 304 (not changed) status responses. By setting
// shouldCacheDatedData to YES, the fetcher will save any response that has a
// last modifed reply header into the fetchHistory. Then any future fetches
// using that same fetchHistory will automatically load the cached response and
// return it to the caller (with a status of 200) in place of the 304 server
// reply.
- (BOOL)shouldCacheDatedData;
- (void)setShouldCacheDatedData:(BOOL)flag;
// Delete the last-modified dates and cached data from the fetch history.
- (void)clearDatedDataHistory;
/// userData is retained for the convenience of the caller
- (id)userData;
- (void)setUserData:(id)theObj;
// properties are retained for the convenience of the caller
- (void)setProperties:(NSDictionary *)dict;
- (NSDictionary *)properties;
- (void)setProperty:(id)obj forKey:(NSString *)key; // pass nil obj to remove property
- (id)propertyForKey:(NSString *)key;
// using the fetcher while a modal dialog is displayed requires setting the
// run-loop modes to include NSModalPanelRunLoopMode
// setting run loop modes does nothing if they are not supported,
// such as on 10.4
- (NSArray *)runLoopModes;
- (void)setRunLoopModes:(NSArray *)modes;
+ (BOOL)doesSupportRunLoopModes;
+ (NSArray *)defaultRunLoopModes;
+ (void)setDefaultRunLoopModes:(NSArray *)modes;
// users who wish to replace GTMHTTPFetcher's use of NSURLConnection
// can do so globally here. The replacement should be a subclass of
// NSURLConnection.
+ (Class)connectionClass;
+ (void)setConnectionClass:(Class)theClass;
// GTM HTTP Logging
// All traffic using GTMHTTPFetcher can be easily logged. Call
// [GTMHTTPFetcher setIsLoggingEnabled:YES];
// to begin generating log files.
// Log files are put into a folder on the desktop called "GTMHTTPDebugLogs"
// unless another directory is specified with +setLoggingDirectory.
// Each run of an application gets a separate set of log files. An html
// file is generated to simplify browsing the run's http transactions.
// The html file includes javascript links for inline viewing of uploaded
// and downloaded data.
// A symlink is created in the logs folder to simplify finding the html file
// for the latest run of the application; the symlink is called
// AppName_http_log_newest.html
// For better viewing of XML logs, use Camino or Firefox rather than Safari.
// Projects may define GTM_HTTPFETCHER_ENABLE_LOGGING to 0 to remove all of the
// logging code (it defaults to 1). By default, any data uploaded via PUT/POST
// w/ and NSInputStream will not be logged. You can enable this logging by
// defining GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING to 1 (it defaults to 0).
@interface GTMHTTPFetcher (GTMHTTPFetcherLogging)
// Note: the default logs directory is ~/Desktop/GTMHTTPDebugLogs; it will be
// created as needed. If a custom directory is set, the directory should
// already exist.
+ (void)setLoggingDirectory:(NSString *)path;
+ (NSString *)loggingDirectory;
// client apps can turn logging on and off
+ (void)setIsLoggingEnabled:(BOOL)flag;
+ (BOOL)isLoggingEnabled;
// client apps can optionally specify process name and date string used in
// log file names
+ (void)setLoggingProcessName:(NSString *)str;
+ (NSString *)loggingProcessName;
+ (void)setLoggingDateStamp:(NSString *)str;
+ (NSString *)loggingDateStamp;