/* * CPURLConnection.j * Foundation * * Created by Francisco Tolmasky. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @import "CPData.j" @import "CPObject.j" @import "CPRunLoop.j" @import "CPURLRequest.j" @import "CPURLResponse.j" @typedef HTTPRequest var CPURLConnectionDelegate = nil; /*! @class CPURLConnection @ingroup foundation @brief Provides loading of a URL request. An interface to downloading content at a specified URL. Using one of the class methods, you can obtain the data. @delegate -(void)connection:(CPURLConnection)connection didFailWithError:(id)error; Called when the connection encounters an error. @param connection the connection that had an error @param error the error, which is either a javascript DOMException or an http status code (javascript number/CPNumber) @delegate -(void)connection:(CPURLConnection)connection didReceiveResponse:(CPHTTPURLResponse)response; Called when the connection receives a response. @param connection the connection that received a response @param response the received response @delegate -(void)connection:(CPURLConnection)connection didReceiveData:(CPString)data; Called when the connection has received data. @param connection the connection that received data @param data the received data @delegate -(void)connectionDidFinishLoading:(CPURLConnection)connection; Called when the URL has finished loading. @param connection the connection that finished loading Class Delegate Method: @delegate -(void)connectionDidReceiveAuthenticationChallenge:(id)connection The class delegate allows you to set global behavior for when authentication challenges (401 status codes) are returned. The recommended way to handle this method is to store a reference to the connection, and then use whatever method you have to authenticate yourself. Once you've authenticated yourself, you should cancel and then start the connection:
[connection cancel];
[connection start];
@param connection the connection that received the authentication challenge. */ @implementation CPURLConnection : CPObject { CPURLRequest _request; id _delegate; BOOL _isCanceled; BOOL _isLocalFileConnection; BOOL _withCredentials @accessors(property=withCredentials); HTTPRequest _HTTPRequest; } + (void)setClassDelegate:(id)delegate { CPURLConnectionDelegate = delegate; } /* Sends a request for the data from a URL. This is the easiest way to obtain data from a URL. @param aRequest contains the URL to request the data from @param aURLResponse not used @param anError not used @return the data at the URL or \c nil if there was an error */ + (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse { var cfHTTPRequest = new CFHTTPRequest(); return [CPURLConnection _sendSynchronousRequest:aRequest returningResponse:aURLResponse withCFHTTPRequest:cfHTTPRequest]; } + (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse withCredentials:(BOOL)withCredentials { var cfHTTPRequest = new CFHTTPRequest(); cfHTTPRequest.setWithCredentials(withCredentials); return [CPURLConnection _sendSynchronousRequest:aRequest returningResponse:aURLResponse withCFHTTPRequest:cfHTTPRequest]; } + (CPData)_sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse withCFHTTPRequest:(CFHTTPRequest)aCFHTTPRequest { try { aCFHTTPRequest.open([aRequest HTTPMethod], [[aRequest URL] absoluteString], NO); var fields = [aRequest allHTTPHeaderFields], key = nil, keys = [fields keyEnumerator]; while ((key = [keys nextObject]) !== nil) aCFHTTPRequest.setRequestHeader(key, [fields objectForKey:key]); aCFHTTPRequest.send([aRequest HTTPBody]); if (!aCFHTTPRequest.success()) return nil; return [CPData dataWithRawString:aCFHTTPRequest.responseText()]; } catch (anException) { } return nil; } /* Creates a url connection with a delegate to monitor the request progress. @param aRequest contains the URL to obtain data from @param aDelegate will be sent messages related to the request progress @return a connection that can be \c started to initiate the request */ + (CPURLConnection)connectionWithRequest:(CPURLRequest)aRequest delegate:(id)aDelegate { return [[self alloc] initWithRequest:aRequest delegate:aDelegate]; } //overloaded method that allows user to set _withCredentials + (CPURLConnection)connectionWithRequest:(CPURLRequest)aRequest delegate:(id)aDelegate withCredentials:(BOOL)withCredentials { var connection = [[self alloc] initWithRequest:aRequest delegate:aDelegate startImmediately:NO]; [connection setWithCredentials:withCredentials]; [connection start]; return connection; } /* Default class initializer. Use one of the class methods instead. @param aRequest contains the URL to contact @param aDelegate will receive progress messages @param shouldStartImmediately whether the \c -start method should be called from here @return the initialized url connection */ - (id)initWithRequest:(CPURLRequest)aRequest delegate:(id)aDelegate startImmediately:(BOOL)shouldStartImmediately { self = [super init]; if (self) { _request = aRequest; _delegate = aDelegate; _isCanceled = NO; _withCredentials = NO; var URL = [_request URL], scheme = [URL scheme]; // Browsers use "file:", Titanium uses "app:" _isLocalFileConnection = scheme === "file" || ((scheme === "http" || scheme === "https") && window.location && (window.location.protocol === "file:" || window.location.protocol === "app:")); _HTTPRequest = new CFHTTPRequest(); if (shouldStartImmediately) [self start]; } return self; } - (id)initWithRequest:(CPURLRequest)aRequest delegate:(id)aDelegate { return [self initWithRequest:aRequest delegate:aDelegate startImmediately:YES]; } /* return the delegate */ - (id)delegate { return _delegate; } /* Start the connection. Not needed if you used the class method +connectionWithRequest:delegate: */ - (void)start { _isCanceled = NO; _HTTPRequest.setWithCredentials(_withCredentials); try { _HTTPRequest.open([_request HTTPMethod], [[_request URL] absoluteString], YES); _HTTPRequest.onreadystatechange = function() { [self _readyStateDidChange]; }; var fields = [_request allHTTPHeaderFields], key = nil, keys = [fields keyEnumerator]; while ((key = [keys nextObject]) !== nil) _HTTPRequest.setRequestHeader(key, [fields objectForKey:key]); _HTTPRequest.send([_request HTTPBody]); } catch (anException) { if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)]) [_delegate connection:self didFailWithError:anException]; } } /* Cancels the current request. */ - (void)cancel { _isCanceled = YES; try { _HTTPRequest.abort(); } // We expect an exception in some browsers like FireFox. catch (anException) { } } - (BOOL)isLocalFileConnection { return _isLocalFileConnection; } /* @ignore */ - (void)_readyStateDidChange { if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState) { var statusCode = _HTTPRequest.status(), URL = [_request URL]; if (statusCode === 401 && [CPURLConnectionDelegate respondsToSelector:@selector(connectionDidReceiveAuthenticationChallenge:)]) [CPURLConnectionDelegate connectionDidReceiveAuthenticationChallenge:self]; else { if ([_delegate respondsToSelector:@selector(connection:didReceiveResponse:)]) { if (_isLocalFileConnection) [_delegate connection:self didReceiveResponse:[[CPURLResponse alloc] initWithURL:URL]]; else { var response = [[CPHTTPURLResponse alloc] initWithURL:URL]; [response _setStatusCode:statusCode]; [response _setAllResponseHeaders:_HTTPRequest.getAllResponseHeaders()]; [_delegate connection:self didReceiveResponse:response]; } } if (!_isCanceled) { if ([_delegate respondsToSelector:@selector(connection:didReceiveData:)]) [_delegate connection:self didReceiveData:_HTTPRequest.responseText()]; if ([_delegate respondsToSelector:@selector(connectionDidFinishLoading:)]) [_delegate connectionDidFinishLoading:self]; } } } [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } /* @ignore */ - (HTTPRequest)_HTTPRequest { return _HTTPRequest; } @end @implementation CPURLConnection (Deprecated) + (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse error:(id)anError { _CPReportLenientDeprecation(self, _cmd, @selector(sendSynchronousRequest:returningResponse:)); return [self sendSynchronousRequest:aRequest returningResponse:aURLResponse]; } - (HTTPRequest)_XMLHTTPRequest { _CPReportLenientDeprecation(self, _cmd, @selector(_HTTPRequest)); return [self _HTTPRequest]; } @end