API  0.9.10
CPURLConnection.j
Go to the documentation of this file.
1 /*
2  * CPURLConnection.j
3  * Foundation
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
25 
26 - (void)connection:(CPURLConnection)anURLConnection didFailWithError:(CPException)anError;
27 - (void)connection:(CPURLConnection)anURLConnection didReceiveData:(CPString)aData;
28 - (void)connection:(CPURLConnection)anURLConnection didReceiveResponse:(CPString)aResponse;
29 - (void)connectionDidFinishLoading:(CPURLConnection)anURLConnection;
30 - (void)connectionDidReceiveAuthenticationChallenge:(CPURLConnection)anURLConnection;
31 
32 @end
33 
34 @typedef HTTPRequest
35 
37 
81 @implementation CPURLConnection : CPObject
82 {
83  CPURLRequest _originalRequest;
84  CPURLRequest _request;
85  id <CPURLConnectionDelegate> _delegate;
86  BOOL _isCanceled;
87  BOOL _isLocalFileConnection;
88 
89  HTTPRequest _HTTPRequest;
90 
91  CPOperationQueue _operationQueue;
92  CPOperation _connectionOperation;
93 }
94 
95 + (void)setClassDelegate:(id <CPURLConnectionDelegate>)delegate
96 {
98 }
99 
100 /*
101  Sends a request for the data from a URL. This is the easiest way to obtain data from a URL.
102  @param aRequest contains the URL to request the data from
103  @param aURLResponse not used
104  @param anError not used
105  @return the data at the URL or \c nil if there was an error
106 */
107 + (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse
108 {
109  try
110  {
111  var aCFHTTPRequest = new CFHTTPRequest();
112  aCFHTTPRequest.setWithCredentials([aRequest withCredentials]);
113 
114  aCFHTTPRequest.open([aRequest HTTPMethod], [[aRequest URL] absoluteString], NO);
115 
116  var fields = [aRequest allHTTPHeaderFields],
117  key = nil,
118  keys = [fields keyEnumerator];
119 
120  while ((key = [keys nextObject]) !== nil)
121  aCFHTTPRequest.setRequestHeader(key, [fields objectForKey:key]);
122 
123  aCFHTTPRequest.send([aRequest HTTPBody]);
124 
125  if (!aCFHTTPRequest.success())
126  return nil;
127 
128  return [CPData dataWithRawString:aCFHTTPRequest.responseText()];
129  }
130  catch (anException)
131  {
132  }
133 
134  return nil;
135 }
136 
137 /*
138  Loads the data for a URL request and executes a function on an operation queue when the request completes or fails.
139  @param aRequest contains the URL to obtain data from.
140  @param aQueue The operation queue to which the function is dispatched when the request completes or failed.
141  @param aHandler The function to execute.
142  @discussion If the request completes successfully, the data parameter of the function contains the resource data, and the error parameter is nil. If the request fails, the data parameter is nil and the error parameter contain information about the failure.
143 */
144 + (CPURLConnection)sendAsynchronousRequest:(CPURLRequest)aRequest queue:(CPOperationQueue)aQueue completionHandler:(Function)aHandler
145 {
146  return [[self alloc] _initWithRequest:aRequest queue:aQueue completionHandler:aHandler];
147 }
148 
149 /*
150  Creates a url connection with a delegate to monitor the request progress.
151  @param aRequest contains the URL to obtain data from
152  @param aDelegate will be sent messages related to the request progress
153  @return a connection that can be \c started to initiate the request
154 */
155 + (CPURLConnection)connectionWithRequest:(CPURLRequest)aRequest delegate:(id)aDelegate
156 {
157  return [[self alloc] initWithRequest:aRequest delegate:aDelegate];
158 }
159 
160 /*
161  Default class initializer. Use one of the class methods instead.
162  @param aRequest contains the URL to contact
163  @param aDelegate will receive progress messages
164  @param shouldStartImmediately whether the \c -start method should be called from here
165  @return the initialized url connection
166 */
167 - (id)initWithRequest:(CPURLRequest)aRequest delegate:(id <CPURLConnectionDelegate>)aDelegate startImmediately:(BOOL)shouldStartImmediately
168 {
169  self = [super init];
170 
171  if (self)
172  {
173  _delegate = aDelegate;
174  _operationQueue = nil;
175  _connectionOperation = nil;
176 
177  [self _initWithRequest:aRequest];
178  }
179 
180  if (shouldStartImmediately)
181  [self start];
182 
183  return self;
184 }
185 
186 - (void)_initWithRequest:(CPURLRequest)aRequest
187 {
188  _request = aRequest;
189  _originalRequest = [aRequest copy];
190  _isCanceled = NO;
191 
192  var URL = [_request URL],
193  scheme = [URL scheme];
194 
195  // Browsers use "file:", Titanium uses "app:"
196  _isLocalFileConnection = scheme === "file" ||
197  ((scheme === "http" || scheme === "https") &&
198  window.location &&
199  (window.location.protocol === "file:" || window.location.protocol === "app:"));
200 
201  _HTTPRequest = new CFHTTPRequest();
202  _HTTPRequest.setTimeout([aRequest timeoutInterval] * 1000);
203  _HTTPRequest.setWithCredentials([aRequest withCredentials]);
204 }
205 
206 - (id)_initWithRequest:(CPURLRequest)aRequest queue:(CPOperationQueue)aQueue completionHandler:(Function)aHandler
207 {
208  self = [super init];
209 
210  if (self)
211  {
212  _delegate = nil;
213  _operationQueue = aQueue;
214  _connectionOperation = [[_AsynchronousConnectionOperation alloc] initWithFunction:aHandler];
215 
216  [self _initWithRequest:aRequest];
217  [self start];
218  }
219 
220  return self;
221 }
222 
223 - (id)initWithRequest:(CPURLRequest)aRequest delegate:(id <CPURLConnectionDelegate>)aDelegate
224 {
225  return [self initWithRequest:aRequest delegate:aDelegate startImmediately:YES];
226 }
227 
228 /*
229  return the delegate
230 */
231 - (id)delegate
232 {
233  return _delegate;
234 }
235 
236 /*
237  Start the connection. Not needed if you used the class method +connectionWithRequest:delegate:
238 */
239 - (void)start
240 {
241  _isCanceled = NO;
242 
243  try
244  {
245  _HTTPRequest.open([_request HTTPMethod], [[_request URL] absoluteString], YES);
246 
247  _HTTPRequest.onreadystatechange = function() { [self _readyStateDidChange]; };
248  _HTTPRequest.ontimeout = function() { [self _didTimeout]; };
249 
250  var fields = [_request allHTTPHeaderFields],
251  key = nil,
252  keys = [fields keyEnumerator];
253 
254  while ((key = [keys nextObject]) !== nil)
255  _HTTPRequest.setRequestHeader(key, [fields objectForKey:key]);
256 
257  _HTTPRequest.send([_request HTTPBody]);
258  }
259  catch (anException)
260  {
261  [self _sendDelegateDidFailWithError:anException];
262  }
263 }
264 
265 - (void)_sendDelegateDidFailWithError:(CPException)anException
266 {
267  if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)])
268  [_delegate connection:self didFailWithError:anException];
269  else if (_connectionOperation !== nil)
270  [self _connectionOperationDidReceiveResponse:nil data:nil error:anException];
271 }
272 
273 /*
274  Cancels the current request.
275 */
276 - (void)cancel
277 {
278  _isCanceled = YES;
279 
280  try
281  {
282  _HTTPRequest.abort();
283 
284  if (_connectionOperation)
285  [_connectionOperation cancel];
286  }
287  // We expect an exception in some browsers like FireFox.
288  catch (anException)
289  {
290  }
291 }
292 
294 {
295  return _isLocalFileConnection;
296 }
297 
301 - (void)_didTimeout
302 {
303  var exception = [CPException exceptionWithName:@"Timeout exception"
304  reason:"The request timed out."
305  userInfo:@{}];
306 
307  [self _sendDelegateDidFailWithError:exception];
308 }
309 /* @ignore */
310 - (void)_readyStateDidChange
311 {
312  if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState && !_HTTPRequest.isTimeoutRequest())
313  {
314  var statusCode = _HTTPRequest.status(),
315  URL = [_request URL];
316 
317  if (statusCode === 401 && [CPURLConnectionDelegate respondsToSelector:@selector(connectionDidReceiveAuthenticationChallenge:)])
318  [CPURLConnectionDelegate connectionDidReceiveAuthenticationChallenge:self];
319  else
320  {
321  var response;
322 
323  if (_isLocalFileConnection)
324  response = [[CPURLResponse alloc] initWithURL:URL];
325  else
326  {
327  response = [[CPHTTPURLResponse alloc] initWithURL:URL];
328  [response _setStatusCode:statusCode];
329  [response _setAllResponseHeaders:_HTTPRequest.getAllResponseHeaders()];
330  }
331 
332  if ([_delegate respondsToSelector:@selector(connection:didReceiveResponse:)])
333  [_delegate connection:self didReceiveResponse:response];
334 
335  if (!_isCanceled)
336  {
337  if ([_delegate respondsToSelector:@selector(connection:didReceiveData:)])
338  [_delegate connection:self didReceiveData:_HTTPRequest.responseText()];
339  else if (_connectionOperation !== nil)
340  [self _connectionOperationDidReceiveResponse:response data:_HTTPRequest.responseText() error:nil];
341 
342  if ([_delegate respondsToSelector:@selector(connectionDidFinishLoading:)])
343  [_delegate connectionDidFinishLoading:self];
344  }
345  }
346  }
347 
348  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
349 }
350 
351 /* @ignore */
352 - (HTTPRequest)_HTTPRequest
353 {
354  return _HTTPRequest;
355 }
356 
357 - (void)_connectionOperationDidReceiveResponse:(CPURLResponse)aResponse data:(CPData)aData error:(CPError)anError
358 {
359  [_connectionOperation _setResponse:aResponse data:aData error:anError];
360 
361  if (_operationQueue)
362  [_operationQueue addOperation:_connectionOperation];
363  else
364  {
365  // Do we need to send CPOperation KVO notifications ?
366  [_connectionOperation main];
367  }
368 }
369 
370 @end
371 
372 /* @ignore */
373 @implementation _AsynchronousConnectionOperation : CPOperation
374 {
375  BOOL _didReceiveResponse;
376 
377  CPURLResponse _response;
378  CPData _data;
379  CPError _error;
380  Function _operationFunction;
381 }
382 
383 /* @ignore */
384 - (id)initWithFunction:(Function)aFunction
385 {
386  self = [super init];
387 
388  if (self)
389  {
390  _didReceiveResponse = NO;
391  _response = nil;
392  _data = nil;
393  _error = nil;
394  _operationFunction = aFunction;
395  }
396 
397  return self;
398 }
399 
400 - (void)_setResponse:(CPURLResponse)aResponse data:(CPData)aData error:(CPError)anError
401 {
402  _didReceiveResponse = YES;
403  _response = aResponse;
404  _data = aData;
405  _error = anError;
406 }
407 
408 /* @ignore */
409 - (void)main
410 {
411  _operationFunction(_response, _data, _error);
412 }
413 
414 /* @ignore */
415 - (BOOL)isReady
416 {
417  return (_didReceiveResponse && [super isReady]);
418 }
419 
420 @end
421 
423 
424 + (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse error:(id)anError
425 {
426  _CPReportLenientDeprecation(self, _cmd, @selector(sendSynchronousRequest:returningResponse:));
427 
428  return [self sendSynchronousRequest:aRequest returningResponse:aURLResponse];
429 }
430 
431 - (HTTPRequest)_XMLHTTPRequest
432 {
433  _CPReportLenientDeprecation(self, _cmd, @selector(_HTTPRequest));
434 
435  return [self _HTTPRequest];
436 }
437 
438 @end
439 
441 
446 {
447  return _originalRequest;
448 }
449 
454 {
455  return _request;
456 }
457 
462 {
463  return _connectionOperation;
464 }
465 
466 @end
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
id initWithRequest:delegate:(CPURLRequest aRequest, [delegate] id< CPURLConnectionDelegate > aDelegate)
id initWithRequest:delegate:startImmediately:(CPURLRequest aRequest, [delegate] id< CPURLConnectionDelegate > aDelegate, [startImmediately] BOOL shouldStartImmediately)
The main run loop for the application.
Definition: CPRunLoop.h:2
A Cappuccino wrapper for any data type.
Definition: CPData.h:2
Provides loading of a URL request.
CPData sendSynchronousRequest:returningResponse:(CPURLRequest aRequest, [returningResponse]/*{ */CPURLResponse/*} */aURLResponse)
CPRunLoop currentRunLoop()
Definition: CPRunLoop.j:232
An immutable string (collection of characters).
Definition: CPString.h:2
Protocol agnostic information about a request to a specific URL.
Definition: CPURLResponse.h:2
CPData dataWithRawString:(CPString aString)
Definition: CPData.j:45
CPDate limitDateForMode:(CPString aMode)
Definition: CPRunLoop.j:342
Used for encapsulating, presenting, and recovery from errors.
Definition: CPError.h:2
HTTPRequest var CPURLConnectionDelegate
CPDictionary allHTTPHeaderFields()
Definition: CPURLRequest.j:278
Represents an operation that can be run in an CPOperationQueue.
Definition: CPOperation.h:2
id init()
Definition: CPObject.j:145
CompletionHandlerAgent prototype completionHandler
Contains data obtained during a request made with CPURLConnection.
Definition: CPURLRequest.h:2
CPException exceptionWithName:reason:userInfo:(CPString aName, [reason] CPString aReason, [userInfo] CPDictionary aUserInfo)
Definition: CPException.j:94
id alloc()
Definition: CPObject.j:130
Represents an operation queue that can run CPOperations.