/// <summary> /// Asychronously Pad a stream with a sequence of bytes /// </summary> /// <param name="stream">Target <see cref="Stream"/></param> /// <param name="count">Number of bytes to pad</param> /// <param name="value">Byte value to use for padding</param> /// <param name="result">The <see cref="Result"/> instance to be returned by the call.</param> /// <returns>Synchronization handle for completion of padding action.</returns> public static Result Pad(this Stream stream, long count, byte value, Result result) { if (count < 0) { throw new ArgumentException("count must be non-negative"); } // NOTE (steveb): intermediary copy steps already have a timeout operation, no need to limit the duration of the entire copy operation if (count == 0) { result.Return(); } else { // initialize buffer so we can write in large chunks if need be byte[] bytes = new byte[(int)Math.Min(4096L, count)]; for (int i = 0; i < bytes.Length; ++i) { bytes[i] = value; } // use new task environment so we don't copy the task state over and over again TaskEnv.ExecuteNew(() => Coroutine.Invoke(Pad_Helper, stream, count, bytes, result)); } return(result); }
/// <summary> /// Asynchronous copying of one stream to another. /// </summary> /// <param name="source">Source <see cref="Stream"/>.</param> /// <param name="target">Target <see cref="Stream"/>.</param> /// <param name="length">Number of bytes to copy from source to target.</param> /// <param name="result">The <see cref="Result"/> instance to be returned by the call.</param> /// <returns>Synchronization handle for the number of bytes copied.</returns> public static Result <long> CopyToStream(this Stream source, Stream target, long length, Result <long> result) { if (!SysUtil.UseAsyncIO) { return(AsyncUtil.Fork(() => CopyToStream(source, target, length), result)); } // NOTE (steveb): intermediary copy steps already have a timeout operation, no need to limit the duration of the entire copy operation if ((source == Stream.Null) || (length == 0)) { result.Return(0); } else if (source.IsStreamMemorized() && target.IsStreamMemorized()) { // source & target are memory streams; let's do the copy inline as fast as we can result.Return(CopyToStream(source, target, length)); } else { // use new task environment so we don't copy the task state over and over again TaskEnv.ExecuteNew(() => Coroutine.Invoke(CopyTo_Helper, source, target, length, result)); } return(result); }
/// <summary> /// Invoke an action in the context of a service feature. /// </summary> /// <remarks> /// Assumes that there exists a current <see cref="DreamContext"/> that belongs to a request to another feature of this service. /// </remarks> /// <param name="verb">Http verb.</param> /// <param name="path">Feature path.</param> /// <param name="handler">Action to perform in this context.</param> /// <returns>Exception thrown by handler or null.</returns> public Exception InvokeInServiceContext(string verb, string path, Action handler) { if (handler == null) { throw new ArgumentNullException("handler"); } if (string.IsNullOrEmpty(verb)) { throw new ArgumentNullException("verb"); } if (path == null) { throw new ArgumentNullException("path"); } // create new new environment for execution XUri uri = Self.AtPath(path); DreamContext current = DreamContext.Current; Exception e = TaskEnv.ExecuteNew(delegate { DreamFeatureStage[] stages = new[] { new DreamFeatureStage("InServiceInvokeHandler", InServiceInvokeHandler, DreamAccess.Private) }; // BUGBUGBUG (steveb): when invoking a remote function this way, we're are not running the proloques and epilogues, which leads to errors; // also dream-access attributes are being ignored (i.e. 'public' vs. 'private') DreamMessage message = DreamUtil.AppendHeadersToInternallyForwardedMessage(current.Request, DreamMessage.Ok()); var context = current.CreateContext(verb, uri, new DreamFeature(this, Self, 0, stages, verb, path), message); context.AttachToCurrentTaskEnv(); // pass along host and public-uri information handler(); }, TimerFactory); return(e); }
private static string CachedWebGet(XUri uri, double?ttl, bool?nilIfMissing) { // fetch message from cache or from the web string result; lock (_webTextCache) { if (_webTextCache.TryGetValue(uri, out result)) { return(result); } } // do the web request Result <DreamMessage> response = new Result <DreamMessage>(); Plug.New(uri).WithTimeout(DEFAULT_WEB_TIMEOUT).InvokeEx("GET", DreamMessage.Ok(), response); DreamMessage message = response.Wait(); try { // check message status if (!message.IsSuccessful) { if (nilIfMissing.GetValueOrDefault()) { return(null); } return(message.Status == DreamStatus.UnableToConnect ? string.Format("(unable to fetch text document from uri [status: {0} ({1}), message: \"{2}\"])", (int)message.Status, message.Status, message.ToDocument()["message"].AsText) : string.Format("(unable to fetch text document from uri [status: {0} ({1})])", (int)message.Status, message.Status)); } // check message size Result resMemorize = message.Memorize(InsertTextLimit, new Result()).Block(); if (resMemorize.HasException) { return(nilIfMissing.GetValueOrDefault() ? null : "(text document is too large)"); } // detect encoding and decode response var stream = message.AsStream(); var encoding = stream.DetectEncoding() ?? message.ContentType.CharSet; result = encoding.GetString(stream.ReadBytes(-1)); } finally { message.Close(); } // start timer to clean-up cached result lock (_webTextCache) { _webTextCache[uri] = result; } double timeout = Math.Min(60 * 60 * 24, Math.Max(ttl ?? MinCacheTtl, 60)); TaskEnv.ExecuteNew(() => TaskTimer.New(TimeSpan.FromSeconds(timeout), timer => { lock (_webTextCache) { _webTextCache.Remove((XUri)timer.State); } }, uri, TaskEnv.None)); return(result); }
/// <summary> /// Asynchrounously copy a <see cref="Stream"/> to several targets /// </summary> /// <param name="source">Source <see cref="Stream"/></param> /// <param name="targets">Array of target <see cref="Stream"/> objects</param> /// <param name="length">Number of bytes to copy from source to targets</param> /// <param name="result">The <see cref="Result"/> instance to be returned by the call.</param> /// <returns>Synchronization handle for the number of bytes copied to each target.</returns> public static Result <long?[]> CopyTo(this Stream source, Stream[] targets, long length, Result <long?[]> result) { // NOTE (steveb): intermediary copy steps already have a timeout operation, no need to limit the duration of the entire copy operation if ((source == Stream.Null) || (length == 0)) { long?[] totals = new long?[targets.Length]; for (int i = 0; i < totals.Length; ++i) { totals[i] = 0; } result.Return(totals); } else { // use new task environment so we don't copy the task state over and over again TaskEnv.ExecuteNew(() => Coroutine.Invoke(CopyTo_Helper, source, targets, length, result)); } return(result); }
private Result <long> CopyStream(Action <string> activity, Stream source, Stream target, long length, Result <long> result) { // NOTE (steveb): intermediary copy steps already have a timeout operation, no need to limit the duration of the entire copy operation if ((source == Stream.Null) || (length == 0)) { activity("return CopyStream 1"); result.Return(0); } else if (!SysUtil.UseAsyncIO || (source.IsStreamMemorized() && target.IsStreamMemorized())) { // source & target are memory streams; let's do the copy inline as fast as we can byte[] buffer = new byte[StreamUtil.BUFFER_SIZE]; long total = 0; while (length != 0) { long count = source.Read(buffer, 0, buffer.Length); if (count == 0) { break; } target.Write(buffer, 0, (int)count); total += count; length -= count; } activity("return CopyStream 2"); result.Return(total); } else { // use new task environment so we don't copy the task state over and over again TaskEnv.ExecuteNew(delegate() { activity("pre CopyStream_Handler"); Coroutine.Invoke(CopyStream_Handler, activity, source, target, length, result); activity("post CopyStream_Handler"); }); } return(result); }
//--- Methods --- internal void Handler(Result <DreamMessage> result) { DreamMessage request; // check if previous feature handler threw an exception try { if (result.HasException) { DreamAbortException abort = result.Exception as DreamAbortException; if (abort != null) { // extract contained message request = abort.Response; } else { // check if this is a cached response we need to forward DreamCachedResponseException cached = result.Exception as DreamCachedResponseException; if (cached != null) { _response.Throw(cached); return; } // convert exception into message DreamMessage exceptionMessage = null; ExceptionTranslator[] translators = _context.Feature.ExceptionTranslators; if (translators != null) { bool locallyAttachedContext = false; try { if (DreamContext.CurrentOrNull == null) { _context.AttachToCurrentTaskEnv(); locallyAttachedContext = true; } foreach (var translate in translators) { exceptionMessage = translate(_context, result.Exception); if (exceptionMessage != null) { break; } } } finally { if (locallyAttachedContext) { _context.DetachFromTaskEnv(); } } } if (exceptionMessage == null) { _log.ErrorExceptionFormat(result.Exception, "handler for {0}:{1} failed ({2})", _context.Verb, _context.Uri.ToString(false), _previousName); exceptionMessage = DreamMessage.InternalError(result.Exception); } request = exceptionMessage; } } else { request = result.Value; } } catch (Exception e) { if (result.HasException) { _log.ErrorExceptionFormat(result.Exception, "handler for {0}:{1} failed ({2}), cascading via processing exception '{3}'", _context.Verb, _context.Uri.ToString(false), _previousName, e); request = DreamMessage.InternalError(result.Exception); } else { _log.ErrorExceptionFormat(e, "handler for {0}:{1} failed completing stage '{2}'", _context.Verb, _context.Uri.ToString(false), _previousName); request = DreamMessage.InternalError(e); } } // check if feature handler can handle this message if (!MainStage || request.IsSuccessful) { TaskEnv.ExecuteNew(() => Handler_DreamMessage(request)); } else { // non-success messages skip the main stage _response.Return(request); } }