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);
            }
        }
Exemple #5
0
        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();
            }
        }
Exemple #6
0
        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);
                }
            }
        }
Exemple #7
0
        //--- 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();
            }
        }
Exemple #8
0
        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();
            }
        }
Exemple #9
0
        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);
        }