/// <summary> /// Invoke the plug, but leave the stream unread so that the returned <see cref="DreamMessage"/> can be streamed. /// </summary> /// <param name="verb">Request verb.</param> /// <param name="request">Request message.</param> /// <param name="result">The <see cref="Result{DreamMessage}"/>instance to be returned by this method.</param> /// <returns>Synchronization handle.</returns> public Result<DreamMessage> InvokeEx(string verb, DreamMessage request, Result<DreamMessage> result) { if(verb == null) { throw new ArgumentNullException("verb"); } if(request == null) { throw new ArgumentNullException("request"); } if(request.Status != DreamStatus.Ok) { throw new ArgumentException("request status must be 200 (Ok)"); } if(result == null) { throw new ArgumentNullException("response"); } // determine which factory has the best match IPlugEndpoint match; XUri normalizedUri; FindPlugEndpoint(Uri, out match, out normalizedUri); // check if we found a match if(match == null) { request.Close(); result.Return(new DreamMessage(DreamStatus.NoEndpointFound, null, XDoc.Empty)); return result; } // add matching cookies from service or from global cookie jar DreamCookieJar cookies = CookieJar; // prepare request try { request = PreProcess(verb, Uri, normalizedUri, _headers, cookies, request); // check if custom pre-processing handlers are registered if(_preHandlers != null) { foreach(var handler in _preHandlers) { request = handler(verb, Uri, normalizedUri, request) ?? new DreamMessage(DreamStatus.RequestIsNull, null, XDoc.Empty); if(request.Status != DreamStatus.Ok) { result.Return(request); return result; } } } } catch(Exception e) { request.Close(); result.Return(new DreamMessage(DreamStatus.RequestFailed, null, new XException(e))); return result; } // Note (arnec): Plug never throws, so we usurp the passed result if it has a timeout // setting the result timeout on inner result manually var outerTimeout = result.Timeout; if(outerTimeout != TimeSpan.MaxValue) { result.Timeout = TimeSpan.MaxValue; } // if the governing result has a shorter timeout than the plug, it superceeds the plug timeout var timeout = outerTimeout < Timeout ? outerTimeout : Timeout; // prepare response handler var inner = new Result<DreamMessage>(timeout, TaskEnv.None).WhenDone( v => { try { var message = PostProcess(verb, Uri, normalizedUri, _headers, cookies, v); // check if custom post-processing handlers are registered if((message.Status == DreamStatus.MovedPermanently || message.Status == DreamStatus.Found || message.Status == DreamStatus.TemporaryRedirect) && AutoRedirect && request.IsCloneable ) { var redirectPlug = new Plug(message.Headers.Location, Timeout, Headers, null, null, null, CookieJar, (ushort)(MaxAutoRedirects - 1)); var redirectMessage = request.Clone(); request.Close(); redirectPlug.InvokeEx(verb, redirectMessage, new Result<DreamMessage>()).WhenDone(result.Return); } else { request.Close(); if(_postHandlers != null) { foreach(var handler in _postHandlers) { message = handler(verb, Uri, normalizedUri, message) ?? new DreamMessage(DreamStatus.ResponseIsNull, null, XDoc.Empty); } } result.Return(message); } } catch(Exception e) { request.Close(); result.Return(new DreamMessage(DreamStatus.ResponseFailed, null, new XException(e))); } }, e => { // an exception occurred somewhere during processing (not expected, but it could happen) request.Close(); var status = DreamStatus.RequestFailed; if(e is TimeoutException) { status = DreamStatus.RequestConnectionTimeout; } result.Return(new DreamMessage(status, null, new XException(e))); } ); // invoke message handler Coroutine.Invoke(match.Invoke, this, verb, normalizedUri, request, inner); return result; }