CURLHandle


Inherits From:
NSURLHandle
Declared In:
CURLHandle.h


Class Description

CURLHandle is a wrapper around a CURL. This is in the public domain, but please report any improvements back to the author (dwood_karelia_com). Be sure to be familiar with CURL and how it works; see http://curl.haxx.se/

The idea is to have it handle http and possibly other schemes too. At this time we don't support writing data (via HTTP PUT) and special situations such as HTTPS and firewall proxies haven't been tried out yet.

This class maintains only basic functionality, any "bells and whistles" should be defined in a category to keep this file as simple as possible.

Each instance is created to be associated with a URL. But we can change the URL and use the previous connection, as the CURL documentation says.

A URL cache is maintained as described in the NSURLHandle documentation. It's just a mutable dictionary, which is apparently about as sophisticated as Apple does. In the future, it might be nice to have some sort of LRU scheme....

Notifications are posted when the cache changes; it's possible for the client to track this cache for debugging or other nefarious purposes.

Note: Comments in methods with this formatting indicate quotes from the headers and documentation for NSURLHandle and are provided to help prove "correctness." Some come from an another document -- perhaps an earlier version of the documentation or release notes, but I can't find the original source. These are marked "(?source)"

The comment "DO NOT INVOKE SUPERCLASS" indicates that NSURLHandle does not provide an implementation available for overriding.


Functions

Synopsis:

size_t curlBodyFunction(void *ptr, size_t size, size_t nmemb, void *inSelf);
size_t curlHeaderFunction(void *ptr, size_t size, size_t nmemb, void *inSelf);

Description:

Callbacks from reading a chunk of data. Since we pass "self" in as the "data pointer", we can use that to get back into Objective C and do the work with the class.


Global Variables

Synopsis:

NSString *CURLHandleCacheDeleteNotification;

Description:

The cache has been removed

Synopsis:

NSString *CURLHandleCacheCreateNotification;

Description:

The cache has been created

Synopsis:

NSString *CURLHandleCacheChangeNotification;

Description:

The cache has been changed

Synopsis:

NSString *CURLHandleCreatedNotification;

Description:

A handle has been created; the object is the handle itself.

Synopsis:

NSMutableDictionary *sCurlCache;

Description:

Cache of URL contents, keyed by URL

Synopsis:

NSMutableSet *sAcceptedURLs;

Description:

Set of URLs that CURLHandle will process

Synopsis:

BOOL sAcceptAllHTTP;

Description:

YES if CURLHandle will accept all HTTP

Synopsis:

BOOL sAllowsProxy;

Description:

YES if CURLHandle will allow use of a proxy server

Synopsis:

NSString *sProxyUserIDAndPassword;

Description:

Proxy User ID:Password combo for all uses of CURL.


Instance Variables

NSThread *mMainThread;
CURL *mCURL;
char mErrorBuffer[CURL_ERROR_SIZE];
int mResult;
NSURL *mNSURL;
NSMutableData *mHeaderBuffer;
NSString *mHeaderString;
NSMutableDictionary *mStringOptions;
NSDictionary *mProxies;
NSMutableDictionary *mHTTPHeaders;
id mProgressIndicator;
NSPort *mPort;
BOOL mAbortBackground;
FILE *mPutFile;

mMainThreadReference to main thread so thread can determine if it's a background thread or not
mCURLPointer to the actual CURL object that does all the hard work
mErrorBufferBuffer to hold string generated by CURL; this is then converted to an NSString.
mResultResult after performing a CURL operation; it is displayed as an error code in case there was no error string generated.
mNSURLThe instance of NSURL that is the URL to load
mHeaderBufferThe buffer that is filled with data from the header as the download progresses; it's appended to one line at a time.
mHeaderStringThe header buffer, converted into a string, upon demand.
mStringOptionsDictionary of keys(ints) & values (NSStrings) for performing curl_easy_setopt. We store the options in a dictionary and then invoke curl_easy_setopt on each option right before the curl_easy_perform so that we can retain their memory until it is needed.
mProxiesDictionary of proxy information; it's released when the handle is deallocated since it's needed for the transfer.
mHTTPHeadersDictionary of & values (NSStrings) for additional HTTP headers. We store the options in a dictionary and then make use of them right before the curl_easy_perform so that we can retain their memory until it is needed.
mProgressIndicatorA progress indicator, to animate during foreground loads. This will help give some indication of loading progress, though of course you're better off loading in the background.
mPortA port for communicating between the background thread and the foreground thread.
mAbortBackgroundA flag that is set by the foreground thread and read by the background thread; it's an indicator that the user has cancelled.
mPutFileThe FILE stream if putFile: is used. It's only saved so it can be closed after perform


Method Types

CURLHandle-specific interfaces.
+ curlGoodbye
+ curlHelloSignature:acceptAll:
+ curlAcceptURL:
+ curlFlushEntireCache
- curl
- setString:forKey:
- setStringOrNumberObject:forKey:
- setURL:
- url
- setHTTPHeaders:
+ setProxyUserIDAndPassword:
+ setAllowsProxy:
- setPutFile:
- setPutFileOffset:
- getResponseCookies
+ curlVersion
NSURLHandle overrides
+ canInitWithURL:
+ cachedHandleForURL:
- loadInForeground
- curlError
- initWithURL:cached:
- propertyForKey:
- propertyForKeyIfAvailable:
- dealloc
- beginLoadInBackground
- cancelLoadInBackground
- endLoadInBackground
Support Methods
- curlWritePtr:size:number:message:
- curlThreadBackgroundLoad:
- prepareAndPerformCurl
- handlePortMessage:
- headerString


Class Methods

cachedHandleForURL:

+ (NSURLHandle *)cachedHandleForURL:(NSURL *)anURL

Returns the URL handle from the cache that has serviced anURL or another identical URL. Subclasses of NSURLHandle must override this method. Returns nil if there is no such handle.

cachedHandleForURL: should look in the cache (maintained by your subclass) for an existing handle that services an URL identical to the one passed. If so, the cached handle should be returned. If not, a new handle should be created for the URL, stored in the cache, then returned. (?source) We have super cache the handle as well, though it's only to cause the data not to be flushed. Because the superclass is actually abstract (using the whole class cluster mechanism), it's not like we're caching the URL and so is the parent.


canInitWithURL:

+ (BOOL)canInitWithURL:(NSURL *)anURL

Returns whether an URL handle can be initialized with anURL. If anURL uses an unsupported scheme, this method returns NO. Subclasses of NSURLHandle must override this method. to identify which URLs they can service.

Success if either the "all HTTP" switch is on and the URL is an HTTP url, or if it's a member of the set of URLs accepted explicitly.

DO NOT INVOKE SUPERCLASS.


curlAcceptURL:

+ (void)curlAcceptURL:(NSURL *)url

Add an individual URL to the set of URLs that CURLHandle will accept. This is useful when you want to accept only certain URLs but not others. If you want to have CURLHandle handle all HTTPs (which seems to work just fine), just invoke curlHelloSignature:acceptAll: with YES instead of registering individual URLs.


curlFlushEntireCache

+ (void)curlFlushEntireCache

Flush the entire cache of URLs. There doesn't seem to be an NSURLHandle API to do this, so we provide our own.


curlGoodbye

+ (void)curlGoodbye

You must call curlGoodbye at end of program, for example in [NSApplication applicationWillTerminate:].


curlHelloSignature:acceptAll:

+ (void)curlHelloSignature:(NSString *)inSignature acceptAll:(BOOL)inAcceptAllHTTP

Initialize CURLHandle and the underlying CURL. This can be invoked when the program is launched or before any loading is needed. Parameter is YES if all http URLs should be handled by this; NO if only ones registered with curlAcceptURL:

Now all that remains is to inform NSURLHandle of your new subclass; you do this by sending the NSURLHandle class the registerURLHandleClass: message, passing your subclass as the argument. Once this message has been sent, as NSURLHandle is asked to create handles for a given URL, it will in turn ask your subclass if it wants to handle the URL. If your subclass responds YES, NSURLHandle will instantiate your subclass for the URL. (?source)


curlVersion

+ (NSString *)curlVersion

No method description.


setAllowsProxy:

+ (void)setAllowsProxy:(BOOL)inBool

Set whether proxies are allowed or not. Default value is YES. If no, the proxy settings are ignored.


setProxyUserIDAndPassword:

+ (void)setProxyUserIDAndPassword:(NSString *)inString

Set a proxy user id and password, used by all CURLHandle. This should be done before any transfers are made.


Instance Methods

beginLoadInBackground

- (void)beginLoadInBackground

beginLoadInBackground should start a background load of the data, then return. (?source) Called from -loadInBackground, above. Called when a background load begins. This method is provided mainly for subclasses that wish to take advantage of the superclass' failure reporting mechanism.

DO NOT INVOKE SUPERCLASS.

We just set a couple of status flags and then detach the background thread at this point, as long as it's not already happening.


cancelLoadInBackground

- (void)cancelLoadInBackground

Called to cancel a load currently in progress. You should call super's implementation at the end of your implementation of this method.

Finally, your subclass should override cancelLoadInBackground to stop a background load in progress. Once a handle has received a cancelLoadInBackground message, it must not send any further didLoadBytes:loadComplete: or backgroundLoadDidFailWithReason: messages. (?source) This just sets a flag so that the next time the background thread is about to send a message, it will not. However, all current operations will still execute. But we just won't do anything with the results.


curl

- (CURL *)curl

Return the CURL object assocated with this, so categories can have other methods that do curl-specific stuff like curl_easy_getinfo


curlError

- (NSString *)curlError

Convert curl's error buffer into an NSString if possible, or return the result code number as a string. This is pass into backgroundLoadDidFailWithReason to set the failureReason string.


curlThreadBackgroundLoad:

- (void)curlThreadBackgroundLoad:(id)inParam

Method executed in new background thread to load the URL. It sets up an autorelease pool, does the load (which has callbacks), and then sends a port message to indicate that the load is done. The CURLHandle is retained for the duration of this thread, so it won't be deallocated until the thread is done executing.


curlWritePtr:size:number:message:

- (size_t)curlWritePtr:(void *)inPtr size:(size_t)inSize number:(size_t)inNumber message:(int)inMessageID

Continue the writing callback in Objective C; now we have our instance variables.


dealloc

- (void)dealloc

Make the CURLHandle go away.

This will only be invoked after the background thread has completed, since the target of a thread detachment is retained.


endLoadInBackground

- (void)endLoadInBackground

Called by cancelLoadInBackground to halt any background loading. You should call super's implementation at the end of your implementation of this method. DO NOT INVOKE SUPERCLASS


getResponseCookies

- (NSArray *)getResponseCookies

Return the cookie array from the latest request. Equivalent to getting a property of COOKIES.


handlePortMessage:

- (void)handlePortMessage:(NSPortMessage *)portMessage

NSPortDelegate method gets called in the foreground thread. Now we're ready to call our data-processor, which is called from both head and body.


headerString

- (NSString *)headerString

Return the current header, as a string. This is meant to be invoked after all the headers are read; the entire header is cached into a string after converting from raw data.


initWithURL:cached:

- (id)initWithURL:(NSURL *)anURL cached:(BOOL)willCache

initWithURL:cached: is the designated initializer for NSURLHandle; the second argument specifies whether the handle will be placed in the cache. (?source)

Initializes a newly created URL handle with the URL anURL. willCache controls whether the URL handle will cache its data and respond to requests from equivalent URLs for the cached data. Subclasses of NSURLHandle must override this method.

TODO: initWithURL ought to clean up better if init failed; release what was allocated. Note that this will not actually look up a URL in the cache if you specify YES. If you want to get an existing URL from the cache, use cachedHandleForURL:.


loadInForeground

- (NSData *)loadInForeground

The last three methods, loadInForeground, beginLoadInBackground, and endLoadInBackground do the meaty work of your subclass. They are called from resourceData, loadInBackground, and cancelLoadInBackground respectively, after checking the status of the handle. (For instance, resourceData will not call loadInForeground if the handle has already been loaded; it will simply return the existing data.) (?source)

Loads the receiver's data in the synchronously. Called by resourceData. Subclasses of NSURLHandle must override this method.

DO NOT INVOKE SUPERCLASS.


prepareAndPerformCurl

- (void)prepareAndPerformCurl

Actually set up for loading and do the perform. This happens in either the foreground or background thread. Before doing the perform, we collect up all the saved-up string-valued options, and set them right before the perform. This is because we create temporary (autoreleased) c-strings.


propertyForKey:

- (id)propertyForKey:(NSString *)propertyKey

Returns the property for key propertyKey; returns nil if there is no such key. Subclasses of NSURLHandle must override this method.

DO NOT INVOKE SUPERCLASS.


propertyForKeyIfAvailable:

- (id)propertyForKeyIfAvailable:(NSString *)propertyKey

Returns the property for key propertyKey only if the value is already available, i.e., the client doesn't need to do any work.

DO NOT INVOKE SUPERCLASS.

TODO: We can't assume any encoding for header. Perhaps we could look for the encoding value in the header, and try again if it doesn't match?

TODO: Apple defines some keys, but what the heck are they? "Description Forthcoming"....

This first attempts to handle the Apple-defined NSHTTPProperty... keys. Then if it's HEADER we just return the whole header string. If it's COOKIES, we return the cookie as an array; this can be further parsed with parsedCookies:.

Otherwise, we try to get it by just getting a header with that property name (case-insensitive).


setHTTPHeaders:

- (void)setHTTPHeaders:(NSDictionary *)inDict

Add these to the list of HTTP headers (besides cookie, user agent, referer -- see CURLOPT_HTTPHEADER).


setPutFile:

- (void)setPutFile:(NSString *)path

Set the file to be PUT


setPutFileOffset:

- (void)setPutFileOffset:(int)offset

Set the file offset for performing the PUT.


setString:forKey:

- (void)setString:(NSString *)inString forKey:(CURLoption)inCurlOption

Set an option given a CURLoption key. Before transfer, the string will be used to invoke curl_easy_setopt. Categories with convenient APIs can make use of this.


setStringOrNumberObject:forKey:

- (void)setStringOrNumberObject:(id)inString forKey:(CURLoption)inCurlOption

Set an option given a CURLoption key. Before transfer, the object, which must be an NSString or an integer NSNumber will be used to invoke curl_easy_setopt. Categories with convenient APIs can make use of this.


setURL:

- (void)setURL:(NSURL *)inURL

Set the URL related to this CURLHandle. This can actually be changed so the same CURL is used for different URLs, though they must be done sequentially. (See libcurl documentation.) Note that doing so will confuse the cache, since cache is still keyed by original URL.


url

- (NSURL *)url

return the NSURL associated with this CURLHandle