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); }
public Yield GetSiteLogo(DreamContext context, DreamMessage request, Result <DreamMessage> response) { DreamMessage responseMsg = null; DateTime modified = DekiContext.Current.Instance.Storage.GetSiteFileTimestamp(LOGO_LABEL); try { if (modified != DateTime.MinValue) { if (request.CheckCacheRevalidation(modified)) { responseMsg = DreamMessage.NotModified(); } } if (responseMsg == null) { StreamInfo file = DekiContext.Current.Instance.Storage.GetSiteFile(LOGO_LABEL, false); if (file != null) { responseMsg = DreamMessage.Ok(MimeType.PNG, file.Length, file.Stream); //Build the content disposition headers responseMsg.Headers.ContentDisposition = new ContentDisposition(true, file.Modified ?? DateTime.UtcNow, null, null, "logo.png", file.Length); //Set caching headers responseMsg.SetCacheMustRevalidate(modified); } else { responseMsg = DreamMessage.NotFound("Logo has not been uploaded"); } } } catch { if (responseMsg != null) { responseMsg.Close(); } throw; } response.Return(responseMsg); yield break; }
public Yield GetFile(DreamContext context, DreamMessage request, Result <DreamMessage> response) { PageBE parentPage = null; DreamMessage responseMsg = null; ResourceBE fileRevision = GetAttachment(context, request, Permissions.READ, true, false, out parentPage); if (fileRevision.IsHidden) { PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN); } // check if only file information is requested if (context.Verb == Verb.HEAD) { response.Return(new DreamMessage(DreamStatus.Ok, null, fileRevision.MimeType, (long)fileRevision.Size, Stream.Null)); yield break; } try { if (request.CheckCacheRevalidation(fileRevision.Timestamp)) { responseMsg = DreamMessage.NotModified(); } if (responseMsg == null) { #region Preview related parameter parsing string sFormat = context.GetParam("format", string.Empty); string sRatio = context.GetParam("ratio", string.Empty); uint height = context.GetParam <uint>("height", 0); uint width = context.GetParam <uint>("width", 0); string cachedSize = context.GetParam("size", string.Empty); // check 'ratio' parameter RatioType ratio = RatioType.UNDEFINED; if (!string.IsNullOrEmpty(sRatio)) { switch (sRatio.ToLowerInvariant().Trim()) { case "var": case "variable": ratio = RatioType.VARIABLE; break; case "fixed": ratio = RatioType.FIXED; break; default: throw new AttachmentFileRatioInvalidArgumentException(); } } // check 'size' parameter SizeType size = SizeType.UNDEFINED; if (!string.IsNullOrEmpty(cachedSize) && !SysUtil.TryParseEnum(cachedSize.Trim(), out size)) { throw new AttachmentFilesizeInvalidArgumentException(); } // check 'format' parameter FormatType format = FormatType.UNDEFINED; if (!string.IsNullOrEmpty(sFormat) && !SysUtil.TryParseEnum(sFormat.Trim(), out format)) { throw new AttachmentFileFormatInvalidArgumentException(); } #endregion //if any preview related parameters are set, do preview logic. Otherwise return the file StreamInfo file = null; if ((size != SizeType.UNDEFINED && size != SizeType.ORIGINAL) || ratio != RatioType.UNDEFINED || format != FormatType.UNDEFINED || height != 0 || width != 0 ) { file = AttachmentPreviewBL.RetrievePreview(fileRevision, height, width, ratio, size, format); } else { var isMSWebDAV = MSWEBDAV_USER_AGENT_REGEX.IsMatch(request.Headers.UserAgent ?? string.Empty); file = DekiContext.Current.Instance.Storage.GetFile(fileRevision, SizeType.ORIGINAL, !isMSWebDAV); } // prepare response if (file == null) { throw new AttachmentDoesNotExistFatalException(fileRevision.ResourceId, fileRevision.Revision); } if (file.Uri != null) { responseMsg = DreamMessage.Redirect(file.Uri); } else { bool inline = fileRevision.MetaXml.ImageHeight.HasValue; // see if we can use the MimeType map for allowing inlining if (!inline) { // if IE inline security is not disabled bool isIE = false; if (!DekiContext.Current.Instance.EnableUnsafeIEContentInlining) { // check the user agent to see if we're dealing with IE isIE = MSIE_USER_AGENT_REGEX.IsMatch(request.Headers.UserAgent ?? string.Empty); } // see if the mime-type could allow inlining inline = DekiContext.Current.Instance.MimeTypeCanBeInlined(fileRevision.MimeType); if (inline && isIE) { // check whether the creator of the file had unsafecontent permission, to override IE security IList <ResourceBE> revisions = ResourceBL.Instance.GetResourceRevisions(fileRevision.ResourceId, ResourceBE.ChangeOperations.CONTENT, SortDirection.DESC, 1); UserBE lastContentEditor = UserBL.GetUserById(revisions[0].UserId); inline = PermissionsBL.IsUserAllowed(lastContentEditor, parentPage, Permissions.UNSAFECONTENT); } } responseMsg = DreamMessage.Ok(fileRevision.MimeType, file.Length, file.Stream); responseMsg.Headers["X-Content-Type-Options"] = "nosniff"; responseMsg.Headers.ContentDisposition = new ContentDisposition(inline, fileRevision.Timestamp, null, null, fileRevision.Name, file.Length, request.Headers.UserAgent); // MSIE6 will delete a downloaded file before the helper app trying to use it can get to it so we //have to do custom cache control headers for MSIE6 so that the file can actually be opened if (MSIE6_USER_AGENT_REGEX.IsMatch(request.Headers.UserAgent ?? string.Empty)) { responseMsg.Headers["Expires"] = "0"; responseMsg.Headers.Pragma = "cache"; responseMsg.Headers.CacheControl = "private"; } else { responseMsg.SetCacheMustRevalidate(fileRevision.Timestamp); } } } } catch { if (responseMsg != null) { responseMsg.Close(); } throw; } response.Return(responseMsg); yield break; }
//--- Methods --- void IHttpHandler.ProcessRequest(HttpContext httpContext) { var key = new object(); DreamMessage request = null; try { string verb = httpContext.Request.HttpMethod; XUri requestUri = HttpUtil.FromHttpContext(httpContext); _env.AddActivityDescription(key, string.Format("Incoming: {0} {1}", verb, requestUri)); _log.DebugMethodCall("ProcessRequest", verb, requestUri); // create request message request = new DreamMessage(DreamStatus.Ok, new DreamHeaders(httpContext.Request.Headers), MimeType.New(httpContext.Request.ContentType), httpContext.Request.ContentLength, httpContext.Request.InputStream); DreamUtil.PrepareIncomingMessage(request, httpContext.Request.ContentEncoding, string.Format("{0}://{1}{2}", httpContext.Request.Url.Scheme, httpContext.Request.Url.Authority, httpContext.Request.ApplicationPath), httpContext.Request.UserHostAddress, httpContext.Request.UserAgent); // TODO (arnec): should this happen before PrepareIncomingMessage? request.Headers.DreamTransport = _handler.GetRequestBaseUri(httpContext.Request).ToString(); // process message var response = _env.SubmitRequestAsync(verb, requestUri, httpContext.User, request, new Result <DreamMessage>(TimeSpan.MaxValue)).Block(); request.Close(); var item = response.HasException ? DreamMessage.InternalError(response.Exception) : response.Value; // set status if (_log.IsDebugEnabled) { _log.DebugMethodCall("ProcessRequest[Status]", item.Status, String.Format("{0}{1}", httpContext.Request.Url.GetLeftPart(UriPartial.Authority), httpContext.Request.RawUrl).Replace("/index.aspx", "/")); } httpContext.Response.StatusCode = (int)item.Status; // remove internal headers item.Headers.DreamTransport = null; item.Headers.DreamPublicUri = null; // create stream for response (this will force the creation of the 'Content-Length' header as well) Stream stream = item.ToStream(); // copy headers foreach (KeyValuePair <string, string> pair in item.Headers) { _log.TraceMethodCall("ProcessRequest[Header]", pair.Key, pair.Value); httpContext.Response.AppendHeader(pair.Key, pair.Value); } // add set-cookie headers to response if (item.HasCookies) { foreach (DreamCookie cookie in item.Cookies) { httpContext.Response.AppendHeader(DreamHeaders.SET_COOKIE, cookie.ToSetCookieHeader()); } } // send message stream long size = item.ContentLength; if (((size == -1) || (size > 0)) && (stream != Stream.Null)) { stream.CopyTo(httpContext.Response.OutputStream, size, new Result <long>(TimeSpan.MaxValue)).Wait(); } item.Close(); } catch (Exception ex) { _log.ErrorExceptionMethodCall(ex, "CommonRequestHandler"); if (request != null) { request.Close(); } if (httpContext != null) { httpContext.Response.Close(); } } finally { _env.RemoveActivityDescription(key); } }
private Yield ResponseHandler(DreamMessage request, Result<DreamMessage> response, HttpListenerContext httpContext, Action<string> activity, Result result) { DreamMessage item = null; request.Close(); try { activity("begin ResponseHandler"); item = response.HasException ? DreamMessage.InternalError(response.Exception) : response.Value; // set status _log.TraceMethodCall("ResponseHandler: Status", item.Status, httpContext.Request.HttpMethod, httpContext.Request.Url); httpContext.Response.StatusCode = (int)item.Status; // remove internal headers item.Headers.DreamTransport = null; item.Headers.DreamPublicUri = null; // add out-going headers if(item.Headers.Server == null) { item.Headers.Server = ServerSignature; } // create stream for response (this will force the creation of the 'Content-Length' header as well) Stream stream = item.ToStream(); // copy headers httpContext.Response.Headers.Clear(); foreach(KeyValuePair<string, string> pair in item.Headers) { _log.TraceMethodCall("SendHttpResponse: Header", pair.Key, pair.Value); HttpUtil.AddHeader(httpContext.Response, pair.Key, pair.Value); } // add set-cookie headers to response if(item.HasCookies) { foreach(DreamCookie cookie in item.Cookies) { httpContext.Response.Headers.Add(DreamHeaders.SET_COOKIE, cookie.ToSetCookieHeader()); } } // disable keep alive behavior httpContext.Response.KeepAlive = false; // send message stream long size = item.ContentLength; if(((size == -1) || (size > 0)) && (stream != Stream.Null)) { activity(string.Format("pre CopyStream ({0} bytes)", size)); yield return CopyStream(activity, stream, httpContext.Response.OutputStream, size, new Result<long>(DreamHostService.MAX_REQUEST_TIME)).CatchAndLog(_log); activity("post CopyStream"); } activity("pre Flush"); httpContext.Response.OutputStream.Flush(); activity("post Flush"); result.Return(); activity("end ResponseHandler"); } finally { activity(null); if(item != null) { item.Close(); } httpContext.Response.Close(); } }
private void RequestHandler(IAsyncResult ar) { HttpListenerContext httpContext = null; Action<string> activity = null; HttpListener listener = (HttpListener)ar.AsyncState; // try to finish getting the current context try { httpContext = listener.EndGetContext(ar); } catch(Exception e) { _log.WarnExceptionFormat(e, "unable to finish acquiring the request context, unable to handle request"); } // start listening for next request if(!listener.IsListening) { _log.Debug("dropping out of request handler, since the listener is no longer listening"); return; } try { listener.BeginGetContext(RequestHandler, listener); } catch(Exception e) { _log.WarnExceptionFormat(e, "unable to re-aquire context, dropping out of request handler"); return; } // if we didn't succeed in ending the GetContext call, drop out if(httpContext == null) { return; } DreamMessage request = null; try { // finish listening for current context string[] prefixes = new string[listener.Prefixes.Count]; listener.Prefixes.CopyTo(prefixes, 0); XUri requestUri = HttpUtil.FromHttpContext(httpContext); _log.DebugMethodCall("RequestHandler", httpContext.Request.HttpMethod, requestUri); // create request message request = new DreamMessage(DreamStatus.Ok, new DreamHeaders(httpContext.Request.Headers), MimeType.New(httpContext.Request.ContentType), httpContext.Request.ContentLength64, httpContext.Request.InputStream); DreamUtil.PrepareIncomingMessage(request, httpContext.Request.ContentEncoding, prefixes[0], httpContext.Request.RemoteEndPoint.ToString(), httpContext.Request.UserAgent); requestUri = requestUri.AuthorizeDreamInParams(request, _dreamInParamAuthtoken); // check if the request was forwarded through Apache mod_proxy string hostname = requestUri.GetParam(DreamInParam.HOST, null) ?? request.Headers.ForwardedHost ?? request.Headers.Host ?? requestUri.HostPort; activity = new ActivityState(_env, httpContext.Request.HttpMethod, httpContext.Request.Url.ToString(), hostname).Message; activity("RequestHandler"); // process message _env.UpdateInfoMessage(_sourceExternal, null); string verb = httpContext.Request.HttpMethod; _env.SubmitRequestAsync(verb, requestUri, httpContext.User, request, new Result<DreamMessage>(TimeSpan.MaxValue)) .WhenDone(result => Coroutine.Invoke(ResponseHandler, request, result, httpContext, activity, new Result(TimeSpan.MaxValue))); } catch(Exception ex) { _log.ErrorExceptionMethodCall(ex, "RequestHandler"); if(request != null) { request.Close(); } try { DreamMessage response = DreamMessage.InternalError(ex); httpContext.Response.StatusCode = (int)response.Status; Stream stream = response.ToStream(); httpContext.Response.Headers.Clear(); foreach(KeyValuePair<string, string> pair in response.Headers) { HttpUtil.AddHeader(httpContext.Response, pair.Key, pair.Value); } httpContext.Response.KeepAlive = false; long size = response.ContentLength; if(((size == -1) || (size > 0)) && (stream != Stream.Null)) { CopyStream(delegate { }, stream, httpContext.Response.OutputStream, size, new Result<long>(DreamHostService.MAX_REQUEST_TIME)).Block(); } httpContext.Response.OutputStream.Flush(); } catch { httpContext.Response.StatusCode = (int)DreamStatus.InternalError; } httpContext.Response.Close(); if(activity != null) { activity(null); } } }
//--- Methods --- void IHttpHandler.ProcessRequest(HttpContext httpContext) { var activity = _env.CreateActivityDescription(); DreamMessage request = null; try { string verb = httpContext.Request.HttpMethod; XUri requestUri = HttpUtil.FromHttpContext(httpContext); activity.Description = string.Format("Incoming: {0} {1}", verb, requestUri); _log.DebugMethodCall("ProcessRequest", verb, requestUri); // create request message request = new DreamMessage(DreamStatus.Ok, new DreamHeaders(httpContext.Request.Headers), MimeType.New(httpContext.Request.ContentType), httpContext.Request.ContentLength, httpContext.Request.InputStream); DreamUtil.PrepareIncomingMessage(request, httpContext.Request.ContentEncoding, string.Format("{0}://{1}{2}", httpContext.Request.Url.Scheme, httpContext.Request.Url.Authority, httpContext.Request.ApplicationPath), httpContext.Request.UserHostAddress, httpContext.Request.UserAgent); requestUri = requestUri.AuthorizeDreamInParams(request, _handler.AppConfig.DreamInParamAuthToken); // TODO (arnec): should this happen before PrepareIncomingMessage? request.Headers.DreamTransport = _handler.GetRequestBaseUri(httpContext.Request).ToString(); // process message var response = _env.SubmitRequestAsync(verb, requestUri, httpContext.User, request, new Result<DreamMessage>(TimeSpan.MaxValue)).Block(); request.Close(); if(response.HasException) { _log.ErrorExceptionFormat(response.Exception, "Request Failed [{0}:{1}]: {2}", verb, requestUri.Path, response.Exception.Message); } var item = response.HasException ? DreamMessage.InternalError(response.Exception) : response.Value; // set status if(_log.IsDebugEnabled) { _log.DebugMethodCall("ProcessRequest[Status]", item.Status, String.Format("{0}{1}", httpContext.Request.Url.GetLeftPart(UriPartial.Authority), httpContext.Request.RawUrl).Replace("/index.aspx", "/")); } httpContext.Response.StatusCode = (int)item.Status; // remove internal headers item.Headers.DreamTransport = null; item.Headers.DreamPublicUri = null; // create stream for response (this will force the creation of the 'Content-Length' header as well) Stream stream = item.ToStream(); // copy headers foreach(KeyValuePair<string, string> pair in item.Headers) { _log.TraceMethodCall("ProcessRequest[Header]", pair.Key, pair.Value); httpContext.Response.AppendHeader(pair.Key, pair.Value); } // add set-cookie headers to response if(item.HasCookies) { foreach(DreamCookie cookie in item.Cookies) { httpContext.Response.AppendHeader(DreamHeaders.SET_COOKIE, cookie.ToSetCookieHeader()); } } // send message stream long size = item.ContentLength; if(((size == -1) || (size > 0)) && (stream != Stream.Null)) { stream.CopyToStream(httpContext.Response.OutputStream, size, new Result<long>(TimeSpan.MaxValue)).Wait(); } item.Close(); } catch(Exception ex) { _log.ErrorExceptionMethodCall(ex, "CommonRequestHandler"); if(request != null) { request.Close(); } if(httpContext != null) { httpContext.Response.Close(); } } finally { activity.Dispose(); } }
private Yield ResponseHandler(DreamMessage request, Result <DreamMessage> response, HttpListenerContext httpContext, Action <string> activity, Result result) { DreamMessage item = null; request.Close(); try { activity("begin ResponseHandler"); item = response.HasException ? DreamMessage.InternalError(response.Exception) : response.Value; // set status _log.TraceMethodCall("ResponseHandler: Status", item.Status, httpContext.Request.HttpMethod, httpContext.Request.Url); httpContext.Response.StatusCode = (int)item.Status; // remove internal headers item.Headers.DreamTransport = null; item.Headers.DreamPublicUri = null; // add out-going headers if (item.Headers.Server == null) { item.Headers.Server = ServerSignature; } // create stream for response (this will force the creation of the 'Content-Length' header as well) Stream stream = item.ToStream(); // copy headers httpContext.Response.Headers.Clear(); foreach (KeyValuePair <string, string> pair in item.Headers) { _log.TraceMethodCall("SendHttpResponse: Header", pair.Key, pair.Value); HttpUtil.AddHeader(httpContext.Response, pair.Key, pair.Value); } // add set-cookie headers to response if (item.HasCookies) { foreach (DreamCookie cookie in item.Cookies) { httpContext.Response.Headers.Add(DreamHeaders.SET_COOKIE, cookie.ToSetCookieHeader()); } } // disable keep alive behavior httpContext.Response.KeepAlive = false; // send message stream long size = item.ContentLength; if (((size == -1) || (size > 0)) && (stream != Stream.Null)) { activity(string.Format("pre CopyStream ({0} bytes)", size)); yield return(CopyStream(activity, stream, httpContext.Response.OutputStream, size, new Result <long>(DreamHostService.MAX_REQUEST_TIME)).CatchAndLog(_log)); activity("post CopyStream"); } activity("pre Flush"); httpContext.Response.OutputStream.Flush(); activity("post Flush"); result.Return(); activity("end ResponseHandler"); } finally { activity(null); if (item != null) { item.Close(); } httpContext.Response.Close(); } }
private void RequestHandler(IAsyncResult ar) { HttpListenerContext httpContext = null; Action <string> activity = null; HttpListener listener = (HttpListener)ar.AsyncState; // try to finish getting the current context try { httpContext = listener.EndGetContext(ar); } catch (Exception e) { _log.WarnExceptionFormat(e, "unable to finish acquiring the request context, unable to handle request"); } // start listening for next request if (!listener.IsListening) { _log.Debug("dropping out of request handler, since the listener is no longer listening"); return; } try { listener.BeginGetContext(RequestHandler, listener); } catch (Exception e) { _log.WarnExceptionFormat(e, "unable to re-aquire context, dropping out of request handler"); return; } // if we didn't succeed in ending the GetContext call, drop out if (httpContext == null) { return; } DreamMessage request = null; try { // finish listening for current context string[] prefixes = new string[listener.Prefixes.Count]; listener.Prefixes.CopyTo(prefixes, 0); XUri requestUri = HttpUtil.FromHttpContext(httpContext); _log.DebugMethodCall("RequestHandler", httpContext.Request.HttpMethod, requestUri); // create request message request = new DreamMessage(DreamStatus.Ok, new DreamHeaders(httpContext.Request.Headers), MimeType.New(httpContext.Request.ContentType), httpContext.Request.ContentLength64, httpContext.Request.InputStream); DreamUtil.PrepareIncomingMessage(request, httpContext.Request.ContentEncoding, prefixes[0], httpContext.Request.RemoteEndPoint.ToString(), httpContext.Request.UserAgent); requestUri = requestUri.AuthorizeDreamInParams(request, _dreamInParamAuthtoken); // check if the request was forwarded through Apache mod_proxy string hostname = requestUri.GetParam(DreamInParam.HOST, null) ?? request.Headers.ForwardedHost ?? request.Headers.Host ?? requestUri.HostPort; activity = new ActivityState(_env, httpContext.Request.HttpMethod, httpContext.Request.Url.ToString(), hostname).Message; activity("RequestHandler"); // process message _env.UpdateInfoMessage(_sourceExternal, null); string verb = httpContext.Request.HttpMethod; _env.SubmitRequestAsync(verb, requestUri, httpContext.User, request, new Result <DreamMessage>(TimeSpan.MaxValue)) .WhenDone(result => Coroutine.Invoke(ResponseHandler, request, result, httpContext, activity, new Result(TimeSpan.MaxValue))); } catch (Exception ex) { _log.ErrorExceptionMethodCall(ex, "RequestHandler"); if (request != null) { request.Close(); } try { DreamMessage response = DreamMessage.InternalError(ex); httpContext.Response.StatusCode = (int)response.Status; Stream stream = response.ToStream(); httpContext.Response.Headers.Clear(); foreach (KeyValuePair <string, string> pair in response.Headers) { HttpUtil.AddHeader(httpContext.Response, pair.Key, pair.Value); } httpContext.Response.KeepAlive = false; long size = response.ContentLength; if (((size == -1) || (size > 0)) && (stream != Stream.Null)) { CopyStream(delegate { }, stream, httpContext.Response.OutputStream, size, new Result <long>(DreamHostService.MAX_REQUEST_TIME)).Block(); } httpContext.Response.OutputStream.Flush(); } catch { httpContext.Response.StatusCode = (int)DreamStatus.InternalError; } httpContext.Response.Close(); if (activity != null) { activity(null); } } }
private string CachedWebGet(XUri uri, double?ttl, bool?nilIfMissing) { string id = uri.ToString(); // fetch message from cache or from the web CacheEntry result; bool isNew = true; lock (_cacheLookup) { _cacheLookup.TryGetValue(id, out result); } // check if we have a cached entry if (result != null) { _log.DebugFormat("cache hit for '{0}'", result.Id); isNew = false; result.ResetMemoryExpiration(); if (result.Cache != null) { _log.DebugFormat("cache data in memory '{0}'", result.Id); return(result.Cache); } // we have the result on disk, so let's fetch it DreamMessage msg = Storage.At(CACHE_DATA, result.Guid + ".bin").GetAsync().Wait(); if (msg.IsSuccessful) { _log.DebugFormat("cache data pulled from disk"); result.Cache = Encoding.UTF8.GetString(msg.AsBytes()); return(result.Cache); } _log.DebugFormat("unable to fetch cache data from disk: {0}", msg.Status); } else { _log.DebugFormat("new cache item for '{0}'", id); result = new CacheEntry(id, ttl); } // 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.ToStream(); var encoding = stream.DetectEncoding() ?? message.ContentType.CharSet; result.Cache = encoding.GetString(stream.ReadBytes(-1)); } finally { message.Close(); } // start timer to clean-up cached result if (result.Cache != null) { XDoc infoDoc = new XDoc("cache-entry") .Elem("guid", result.Guid) .Elem("id", result.Id) .Elem("expires", result.Expires); lock (result) { Storage.At(CACHE_DATA, result.Guid + ".bin").PutAsync(new DreamMessage(DreamStatus.Ok, null, MimeType.BINARY, Encoding.UTF8.GetBytes(result.Cache))).Wait(); Storage.At(CACHE_INFO, result.Guid + ".xml").PutAsync(infoDoc).Wait(); } if (isNew) { lock (_cacheLookup) { _cacheLookup[id] = result; } // this timer removes the cache entry from disk SetupCacheTimer(result); } } return(result.Cache); }