diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j index b962d107f..911ac5a91 100644 --- a/Foundation/CPURLConnection.j +++ b/Foundation/CPURLConnection.j @@ -112,6 +112,7 @@ var CPURLConnectionDelegate = nil; try { var aCFHTTPRequest = new CFHTTPRequest(); + aCFHTTPRequest.setTimeout([aRequest timeoutInterval] * 1000); aCFHTTPRequest.setWithCredentials([aRequest withCredentials]); aCFHTTPRequest.open([aRequest HTTPMethod], [[aRequest URL] absoluteString], NO); @@ -125,7 +126,7 @@ var CPURLConnectionDelegate = nil; aCFHTTPRequest.send([aRequest HTTPBody]); - if (!aCFHTTPRequest.success()) + if (!aCFHTTPRequest.success() || aCFHTTPRequest.isTimeoutRequest()) return nil; return [CPData dataWithRawString:aCFHTTPRequest.responseText()]; @@ -176,6 +177,7 @@ var CPURLConnectionDelegate = nil; (window.location.protocol === "file:" || window.location.protocol === "app:")); _HTTPRequest = new CFHTTPRequest(); + _HTTPRequest.setTimeout([aRequest timeoutInterval] * 1000); _HTTPRequest.setWithCredentials([aRequest withCredentials]); if (shouldStartImmediately) @@ -210,6 +212,7 @@ var CPURLConnectionDelegate = nil; _HTTPRequest.open([_request HTTPMethod], [[_request URL] absoluteString], YES); _HTTPRequest.onreadystatechange = function() { [self _readyStateDidChange]; }; + _HTTPRequest.ontimeout = function() { [self _timeout]; }; var fields = [_request allHTTPHeaderFields], key = nil, @@ -222,11 +225,16 @@ var CPURLConnectionDelegate = nil; } catch (anException) { - if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)]) - [_delegate connection:self didFailWithError:anException]; + [self _sendDelegateDidFailWithError:anException]; } } +- (void)_sendDelegateDidFailWithError:(CPException)anException +{ + if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)]) + [_delegate connection:self didFailWithError:anException]; +} + /* Cancels the current request. */ @@ -249,10 +257,24 @@ var CPURLConnectionDelegate = nil; return _isLocalFileConnection; } +/*! + @ignore +*/ +- (void)_timeout +{ + var exception = [CPException exceptionWithName:@"Timeout exception" + reason:"The request timed out." + userInfo:@{}]; + + [self _sendDelegateDidFailWithError:exception]; +} + /* @ignore */ - (void)_readyStateDidChange { - if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState) + var isTimeoutRequest = !_HTTPRequest.status() && !_HTTPRequest.responseText(); + + if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState && !_HTTPRequest.isTimeoutRequest()) { var statusCode = _HTTPRequest.status(), URL = [_request URL]; diff --git a/Foundation/CPURLRequest.j b/Foundation/CPURLRequest.j index 8d6a39e3b..e34061c90 100644 --- a/Foundation/CPURLRequest.j +++ b/Foundation/CPURLRequest.j @@ -25,6 +25,12 @@ @import "CPString.j" @import "CPURL.j" +@typedef CPURLRequestCachePolicy +CPURLRequestUseProtocolCachePolicy = 0; +CPURLRequestReloadIgnoringLocalCacheData = 1; +CPURLRequestReturnCacheDataElseLoad = 2; +CPURLRequestReturnCacheDataDontLoad = 3; + /*! @class CPURLRequest @ingroup foundation @@ -35,14 +41,16 @@ */ @implementation CPURLRequest : CPObject { - CPURL _URL @accessors(property=URL); + CPURL _URL @accessors(property=URL); // FIXME: this should be CPData - CPString _HTTPBody @accessors(property=HTTPBody); - CPString _HTTPMethod @accessors(property=HTTPMethod); - BOOL _withCredentials @accessors(property=withCredentials); + CPString _HTTPBody @accessors(property=HTTPBody); + CPString _HTTPMethod @accessors(property=HTTPMethod); + BOOL _withCredentials @accessors(property=withCredentials); - CPDictionary _HTTPHeaderFields @accessors(readonly, getter=allHTTPHeaderFields); + CPDictionary _HTTPHeaderFields @accessors(readonly, getter=allHTTPHeaderFields); + CPTimeInterval _timeoutInterval @accessors(readonly, getter=timeoutInterval); + CPURLRequestCachePolicy _cachePolicy @accessors(readonly, getter=cachePolicy); } /*! @@ -55,6 +63,18 @@ return [[CPURLRequest alloc] initWithURL:aURL]; } +/*! + Creates a request with a specified URL, cachePolicy and timeoutInterval + @param aURL the URL of the request + @param aCachePolicy the cache policy of the request + @param aTimeoutInterval the timeoutInterval of the request + @return a CPURLRequest +*/ ++ (id)requestWithURL:(CPURL)anURL cachePolicy:(CPURLRequestCachePolicy)aCachePolicy timeoutInterval:(CPTimeInterval)aTimeoutInterval +{ + return [[CPURLRequest alloc] initWithURL:anURL cachePolicy:aCachePolicy timeoutInterval:aTimeoutInterval]; +} + /*! Equal to `[receiver initWithURL:nil]`. */ @@ -63,6 +83,27 @@ return [self initWithURL:nil]; } +/*! + Initializes the request with a URL. This is the designated initializer. + + @param aURL the url to set + @param aCachePolicy the cache policy of the request + @param aTimeoutInterval the timeoutInterval of the request + @return the initialized CPURLRequest +*/ +- (id)initWithURL:(CPURL)anURL cachePolicy:(CPURLRequestCachePolicy)aCachePolicy timeoutInterval:(CPTimeInterval)aTimeoutInterval +{ + self = [self initWithURL:anURL]; + + if (self) + { + _cachePolicy = aCachePolicy; + _timeoutInterval = aTimeoutInterval; + } + + return self; +} + /*! Initializes the request with a URL. This is the designated initializer. @@ -81,9 +122,34 @@ _HTTPMethod = @"GET"; _HTTPHeaderFields = @{}; _withCredentials = NO; + _timeoutInterval = 60.0; + _cachePolicy = CPURLRequestUseProtocolCachePolicy; [self setValue:"Thu, 01 Jan 1970 00:00:00 GMT" forHTTPHeaderField:"If-Modified-Since"]; - [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + + switch (_cachePolicy) + { + case CPURLRequestUseProtocolCachePolicy: + // TODO: implement everything about cache... + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataElseLoad: + [self setValue:"max-stale=31536000" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataDontLoad: + [self setValue:"only-if-cached" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReloadIgnoringLocalCacheData: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + default: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + } + [self setValue:"XMLHttpRequest" forHTTPHeaderField:"X-Requested-With"]; } diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 0d34667f2..7d0a93d91 100644 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -105,6 +105,7 @@ GLOBAL(CFHTTPRequest) = function() // by default, all requests will assume that credentials should not be sent. this._nativeRequest.withCredentials = false; + this._nativeRequest.timeout = 60000; var self = this; this._stateChangeHandler = function() @@ -112,7 +113,13 @@ GLOBAL(CFHTTPRequest) = function() determineAndDispatchHTTPRequestEvents(self); }; + this._timeoutHandler = function() + { + dispatchTimeoutHTTPRequestEvents(self); + }; + this._nativeRequest.onreadystatechange = this._stateChangeHandler; + this._nativeRequest.ontimeout = this._timeoutHandler; if (CFHTTPRequest.AuthenticationDelegate !== nil) this._eventDispatcher.addEventListener("HTTP403", function() @@ -209,6 +216,16 @@ CFHTTPRequest.prototype.getResponseHeader = function(/*String*/ aHeader) return this._nativeRequest.getResponseHeader(aHeader); }; +CFHTTPRequest.prototype.setTimeout = function(/*int*/ aTimeout) +{ + this._nativeRequest.timeout = aTimeout; +}; + +CFHTTPRequest.prototype.getTimeout = function(/*int*/ aTimeout) +{ + return this._nativeRequest.timeout +}; + CFHTTPRequest.prototype.getAllResponseHeaders = function() { return this._nativeRequest.getAllResponseHeaders(); @@ -235,7 +252,10 @@ CFHTTPRequest.prototype.send = function(/*Object*/ aBody) if (!this._isOpen) { delete this._nativeRequest.onreadystatechange; + delete this._nativeRequest.ontimeout; + this._nativeRequest.open(this._method, this._URL, this._async, this._user, this._password); + this._nativeRequest.ontimeout = this._timeoutHandler; this._nativeRequest.onreadystatechange = this._stateChangeHandler; } @@ -287,15 +307,25 @@ CFHTTPRequest.prototype.withCredentials = function() return this._nativeRequest.withCredentials; }; +CFHTTPRequest.prototype.isTimeoutRequest = function() +{ + // Can we consider that as a timeout ? + return !this.success() && !this._nativeRequest.response && !this._nativeRequest.responseText && !this._nativeRequest.responseType && !this._nativeRequest.responseURL && !this._nativeRequest.responseXML; +}; + +function dispatchTimeoutHTTPRequestEvents(/*CFHTTPRequest*/ aRequest) +{ + aRequest._eventDispatcher.dispatchEvent({ type:"timeout", request:aRequest}); +} + function determineAndDispatchHTTPRequestEvents(/*CFHTTPRequest*/ aRequest) { - var eventDispatcher = aRequest._eventDispatcher; + var eventDispatcher = aRequest._eventDispatcher, + nativeRequest = aRequest._nativeRequest, + readyStates = ["uninitialized", "loading", "loaded", "interactive", "complete"]; eventDispatcher.dispatchEvent({ type:"readystatechange", request:aRequest}); - var nativeRequest = aRequest._nativeRequest, - readyStates = ["uninitialized", "loading", "loaded", "interactive", "complete"]; - if (readyStates[aRequest.readyState()] === "complete") { var status = "HTTP" + aRequest.status();