public static bool HasValidCache(this ICacheClient cache, IRequest req, string cacheKey, DateTime?checkLastModified, out DateTime?lastModified) { lastModified = null; if (!HostContext.GetPlugin <HttpCacheFeature>().ShouldAddLastModifiedToOptimizedResults()) { return(false); } var ticks = cache.Get <long>(DateCacheKey(cacheKey)); if (ticks > 0) { lastModified = new DateTime(ticks, DateTimeKind.Utc); if (checkLastModified == null) { return(false); } return(checkLastModified.Value <= lastModified.Value); } return(false); }
public List <PostmanRequest> GetRequests(Postman request, string parentId, IEnumerable <Operation> operations) { var ret = new List <PostmanRequest>(); var feature = HostContext.GetPlugin <PostmanFeature>(); var headers = feature.Headers ?? ("Accept: " + MimeTypes.Json); var httpRes = Response as IHttpResponse; if (httpRes != null) { if (request.ssopt != null || request.sspid != null || request.ssid != null) { if (feature.EnableSessionExport != true) { throw new ArgumentException("PostmanFeature.EnableSessionExport is not enabled"); } } if (request.ssopt != null) { Request.AddSessionOptions(request.ssopt); } if (request.sspid != null) { httpRes.Cookies.AddPermanentCookie(SessionFeature.PermanentSessionId, request.sspid); } if (request.ssid != null) { httpRes.Cookies.AddSessionCookie(SessionFeature.SessionId, request.ssid, (HostContext.Config.OnlySendSessionCookiesSecurely && Request.IsSecureConnection)); } } foreach (var op in operations) { if (!HostContext.Metadata.IsVisible(base.Request, op)) { continue; } var allVerbs = op.Actions.Concat( op.Routes.SelectMany(x => x.Verbs)) .SelectMany(x => x == ActionContext.AnyAction ? feature.DefaultVerbsForAny : new List <string> { x }) .ToHashSet(); var propertyTypes = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); op.RequestType.GetSerializableFields() .Each(x => propertyTypes[x.Name] = x.FieldType.AsFriendlyName(feature)); op.RequestType.GetSerializableProperties() .Each(x => propertyTypes[x.Name] = x.PropertyType.AsFriendlyName(feature)); foreach (var route in op.Routes) { var routeVerbs = route.Verbs.Contains(ActionContext.AnyAction) ? feature.DefaultVerbsForAny.ToArray() : route.Verbs; var restRoute = route.ToRestRoute(); foreach (var verb in routeVerbs) { allVerbs.Remove(verb); //exclude handled verbs var routeData = restRoute.QueryStringVariables .Map(x => new PostmanData { key = x, value = "", type = "text", }) .ApplyPropertyTypes(propertyTypes); ret.Add(new PostmanRequest { collectionId = parentId, id = SessionExtensions.CreateRandomSessionId(), method = verb, url = Request.GetBaseUrl().CombineWith(restRoute.Path.ToPostmanPathVariables()), name = GetName(feature, request, op.RequestType, restRoute.Path), description = op.RequestType.GetDescription(), pathVariables = !verb.HasRequestBody() ? restRoute.Variables.Concat(routeData.Select(x => x.key)) .ApplyPropertyTypes(propertyTypes) : null, data = verb.HasRequestBody() ? routeData : null, dataMode = "params", headers = headers, version = 2, time = DateTime.UtcNow.ToUnixTimeMs(), }); } } var emptyRequest = op.RequestType.CreateInstance(); var virtualPath = emptyRequest.ToReplyUrlOnly(); var requestParams = propertyTypes .Map(x => new PostmanData { key = x.Key, value = x.Value, type = "text", }); ret.AddRange(allVerbs.Select(verb => new PostmanRequest { collectionId = parentId, id = SessionExtensions.CreateRandomSessionId(), method = verb, url = Request.GetBaseUrl().CombineWith(virtualPath), pathVariables = !verb.HasRequestBody() ? requestParams.Select(x => x.key) .ApplyPropertyTypes(propertyTypes) : null, name = GetName(feature, request, op.RequestType, virtualPath), description = op.RequestType.GetDescription(), data = verb.HasRequestBody() ? requestParams : null, dataMode = "params", headers = headers, version = 2, time = DateTime.UtcNow.ToUnixTimeMs(), })); } return(ret); }
public object Any(AutoQueryMetadata request) { if (NativeTypesMetadata == null) { throw new NotSupportedException("AutoQueryViewer requries NativeTypesFeature"); } var feature = HostContext.GetPlugin <AutoQueryMetadataFeature>(); var config = feature.AutoQueryViewerConfig; if (config == null) { throw new NotSupportedException("AutoQueryViewerConfig is missing"); } if (config.ServiceBaseUrl == null) { config.ServiceBaseUrl = base.Request.GetBaseUrl(); } if (config.ServiceName == null) { config.ServiceName = HostContext.AppHost.ServiceName; } var userSession = Request.GetSession(); var typesConfig = NativeTypesMetadata.GetConfig(new TypesMetadata { BaseUrl = Request.GetBaseUrl() }); foreach (var type in feature.ExportTypes) { typesConfig.ExportTypes.Add(type); } var metadataTypes = NativeTypesMetadata.GetMetadataTypes(Request, typesConfig, op => HostContext.Metadata.IsAuthorized(op, Request, userSession)); var response = new AutoQueryMetadataResponse { Config = config, UserInfo = new AutoQueryViewerUserInfo { IsAuthenticated = userSession.IsAuthenticated, }, Operations = new List <AutoQueryOperation>(), Types = new List <MetadataType>(), }; var includeTypeNames = new HashSet <string>(); foreach (var op in metadataTypes.Operations) { if (op.Request.Inherits != null && (op.Request.Inherits.Name.StartsWith("QueryDb`") || op.Request.Inherits.Name.StartsWith("QueryData`")) ) { if (config.OnlyShowAnnotatedServices) { var serviceAttrs = op.Request.Attributes.Safe(); var attr = serviceAttrs.FirstOrDefault(x => x.Name + "Attribute" == nameof(AutoQueryViewerAttribute)); if (attr == null) { continue; } } var inheritArgs = op.Request.Inherits.GenericArgs.Safe().ToArray(); response.Operations.Add(new AutoQueryOperation { Request = op.Request.Name, From = inheritArgs.First(), To = inheritArgs.Last(), }); response.Types.Add(op.Request); op.Request.GetReferencedTypeNames().Each(x => includeTypeNames.Add(x)); } } var allTypes = metadataTypes.GetAllTypes(); var types = allTypes.Where(x => includeTypeNames.Contains(x.Name)).ToList(); //Add referenced types to type name search types.SelectMany(x => x.GetReferencedTypeNames()).Each(x => includeTypeNames.Add(x)); //Only need to seek 1-level deep in AutoQuery's (db.LoadSelect) types = allTypes.Where(x => includeTypeNames.Contains(x.Name)).ToList(); response.Types.AddRange(types); response.UserInfo.QueryCount = response.Operations.Count; feature.MetadataFilter?.Invoke(response); return(response); }
public T GetPlugin <T>() where T : class, IPlugin => GetResolver()?.TryResolve <T>() ?? HostContext.GetPlugin <T>();
public object Any(AutoQueryMetadata request) { if (NativeTypesMetadata == null) throw new NotSupportedException("AutoQueryViewer requries NativeTypesFeature"); var feature = HostContext.GetPlugin<AutoQueryFeature>(); var config = feature.AutoQueryViewerConfig; if (config == null) throw new NotSupportedException("AutoQueryViewerConfig is missing"); if (config.ServiceBaseUrl == null) config.ServiceBaseUrl = base.Request.ResolveBaseUrl(); if (config.ServiceName == null) config.ServiceName = HostContext.ServiceName; var typesConfig = NativeTypesMetadata.GetConfig(new TypesMetadata { BaseUrl = Request.GetBaseUrl() }); var metadataTypes = NativeTypesMetadata.GetMetadataTypes(Request, typesConfig); var response = new AutoQueryMetadataResponse { Config = feature.AutoQueryViewerConfig, Operations = new List<AutoQueryOperation>(), Types = new List<MetadataType>(), }; var includeTypeNames = new HashSet<string>(); foreach (var op in metadataTypes.Operations) { if (op.Request.Inherits != null && op.Request.Inherits.Name.StartsWith("QueryBase`")) { if (config.OnlyShowAnnotatedServices) { var serviceAttrs = op.Request.Attributes.Safe(); var attr = serviceAttrs.FirstOrDefault(x => x.Name + "Attribute" == typeof(AutoQueryViewerAttribute).Name); if (attr == null) continue; } var inheritArgs = op.Request.Inherits.GenericArgs.Safe().ToArray(); response.Operations.Add(new AutoQueryOperation { Request = op.Request.Name, From = inheritArgs.First(), To = inheritArgs.Last(), }); response.Types.Add(op.Request); op.Request.GetReferencedTypeNames().Each(x => includeTypeNames.Add(x)); } } var types = metadataTypes.Types.Where(x => includeTypeNames.Contains(x.Name)); //Add referenced types to type name search types.SelectMany(x => x.GetReferencedTypeNames()).Each(x => includeTypeNames.Add(x)); //Only need to seek 1-level deep in AutoQuery's (db.LoadSelect) types = metadataTypes.Types.Where(x => includeTypeNames.Contains(x.Name)); response.Types.AddRange(types); return response; }
public override Task ProcessRequestAsync(IRequest req, IResponse res, string operationName) { var feature = HostContext.GetPlugin <ServerEventsFeature>(); res.ContentType = MimeTypes.ServerSentEvents; res.AddHeader(HttpHeaders.CacheControl, "no-cache"); res.UseBufferedStream = false; res.KeepAlive = true; if (feature.OnInit != null) { feature.OnInit(req); } res.Flush(); var serverEvents = req.TryResolve <IServerEvents>(); var session = req.GetSession(); var userAuthId = session != null ? session.UserAuthId : null; var anonUserId = serverEvents.GetNextSequence("anonUser"); var userId = userAuthId ?? ("-" + anonUserId); var displayName = session.GetSafeDisplayName() ?? "user" + anonUserId; var now = DateTime.UtcNow; var subscriptionId = SessionExtensions.CreateRandomSessionId(); var subscription = new EventSubscription(res) { CreatedAt = now, LastPulseAt = now, Channel = req.QueryString["channel"] ?? EventSubscription.UnknownChannel, SubscriptionId = subscriptionId, UserId = userId, UserName = session != null ? session.UserName : null, DisplayName = displayName, SessionId = req.GetPermanentSessionId(), IsAuthenticated = session != null && session.IsAuthenticated, OnPublish = feature.OnPublish, Meta = { { "userId", userId }, { "displayName", displayName }, { AuthMetadataProvider.ProfileUrlKey, session.GetProfileUrl() ?? AuthMetadataProvider.DefaultNoProfileImgUrl }, } }; if (feature.OnCreated != null) { feature.OnCreated(subscription, req); } var heartbeatUrl = req.ResolveAbsoluteUrl("~/".CombineWith(feature.HeartbeatPath)) .AddQueryParam("id", subscriptionId); var unRegisterUrl = req.ResolveAbsoluteUrl("~/".CombineWith(feature.UnRegisterPath)) .AddQueryParam("id", subscriptionId); var privateArgs = new Dictionary <string, string>(subscription.Meta) { { "id", subscriptionId }, { "unRegisterUrl", unRegisterUrl }, { "heartbeatUrl", heartbeatUrl }, { "heartbeatIntervalMs", ((long)feature.HeartbeatInterval.TotalMilliseconds).ToString(CultureInfo.InvariantCulture) } }; if (feature.OnConnect != null) { feature.OnConnect(subscription, privateArgs); } serverEvents.Register(subscription, privateArgs); var tcs = new TaskCompletionSource <bool>(); subscription.OnDispose = _ => { try { res.EndHttpHandlerRequest(skipHeaders: true); } catch { } tcs.SetResult(true); }; return(tcs.Task); }
public override Task ProcessRequestAsync(IRequest req, IResponse res, string operationName) { if (HostContext.ApplyCustomHandlerRequestFilters(req, res)) { return(EmptyTask); } var feature = HostContext.GetPlugin <ServerEventsFeature>(); var session = req.GetSession(); if (feature.LimitToAuthenticatedUsers && !session.IsAuthenticated) { session.ReturnFailedAuthentication(req); return(EmptyTask); } res.ContentType = MimeTypes.ServerSentEvents; res.AddHeader(HttpHeaders.CacheControl, "no-cache"); res.ApplyGlobalResponseHeaders(); res.UseBufferedStream = false; res.KeepAlive = true; if (feature.OnInit != null) { feature.OnInit(req); } res.Flush(); var serverEvents = req.TryResolve <IServerEvents>(); var userAuthId = session != null ? session.UserAuthId : null; var anonUserId = serverEvents.GetNextSequence("anonUser"); var userId = userAuthId ?? ("-" + anonUserId); var displayName = session.GetSafeDisplayName() ?? "user" + anonUserId; var now = DateTime.UtcNow; var subscriptionId = SessionExtensions.CreateRandomSessionId(); //Handle both ?channel=A,B,C or ?channels=A,B,C var channels = new List <string>(); var channel = req.QueryString["channel"]; if (!string.IsNullOrEmpty(channel)) { channels.AddRange(channel.Split(',')); } channel = req.QueryString["channels"]; if (!string.IsNullOrEmpty(channel)) { channels.AddRange(channel.Split(',')); } if (channels.Count == 0) { channels = EventSubscription.UnknownChannel.ToList(); } var subscription = new EventSubscription(res) { CreatedAt = now, LastPulseAt = now, Channels = channels.ToArray(), SubscriptionId = subscriptionId, UserId = userId, UserName = session != null ? session.UserName : null, DisplayName = displayName, SessionId = req.GetPermanentSessionId(), IsAuthenticated = session != null && session.IsAuthenticated, UserAddress = req.UserHostAddress, OnPublish = feature.OnPublish, Meta = { { "userId", userId }, { "displayName", displayName }, { "channels", string.Join(",", channels) }, { AuthMetadataProvider.ProfileUrlKey, session.GetProfileUrl() ?? AuthMetadataProvider.DefaultNoProfileImgUrl }, } }; if (feature.OnCreated != null) { feature.OnCreated(subscription, req); } if (req.Response.IsClosed) { return(EmptyTask); //Allow short-circuiting in OnCreated callback } var heartbeatUrl = req.ResolveAbsoluteUrl("~/".CombineWith(feature.HeartbeatPath)) .AddQueryParam("id", subscriptionId); var unRegisterUrl = req.ResolveAbsoluteUrl("~/".CombineWith(feature.UnRegisterPath)) .AddQueryParam("id", subscriptionId); subscription.ConnectArgs = new Dictionary <string, string>(subscription.Meta) { { "id", subscriptionId }, { "unRegisterUrl", unRegisterUrl }, { "heartbeatUrl", heartbeatUrl }, { "heartbeatIntervalMs", ((long)feature.HeartbeatInterval.TotalMilliseconds).ToString(CultureInfo.InvariantCulture) }, { "idleTimeoutMs", ((long)feature.IdleTimeout.TotalMilliseconds).ToString(CultureInfo.InvariantCulture) } }; if (feature.OnConnect != null) { feature.OnConnect(subscription, subscription.ConnectArgs); } serverEvents.Register(subscription, subscription.ConnectArgs); var tcs = new TaskCompletionSource <bool>(); subscription.OnDispose = _ => { try { res.EndHttpHandlerRequest(skipHeaders: true); } catch { } tcs.SetResult(true); }; return(tcs.Task); }
public override async Task ExecuteAsync(IRequest req, IResponse res, object requestDto) { if (req.Verb != HttpMethods.Get && req.Verb != HttpMethods.Head) return; if (req.IsInProcessRequest()) return; var feature = HostContext.GetPlugin<HttpCacheFeature>(); if (feature == null) throw new NotSupportedException(ErrorMessages.CacheFeatureMustBeEnabled.LocalizeFmt(req, "[CacheResponse]")); if (feature.DisableCaching) return; var keyBase = "res:" + req.RawUrl; var keySuffix = MimeTypes.GetExtension(req.ResponseContentType); var modifiers = ""; if (req.ResponseContentType == MimeTypes.Json) { string jsonp = req.GetJsonpCallback(); if (jsonp != null) modifiers = "jsonp:" + jsonp.SafeVarName(); } if (VaryByUser) modifiers += (modifiers.Length > 0 ? "+" : "") + "user:"******"+" : "") + "role:" + role; } } } } if (VaryByHeaders != null && VaryByHeaders.Length > 0) { foreach (var header in VaryByHeaders) { var value = req.GetHeader(header); if (!string.IsNullOrEmpty(value)) { modifiers += (modifiers.Length > 0 ? "+" : "") + $"{header}:{value}"; } } } if (modifiers.Length > 0) keySuffix += "+" + modifiers; var cacheInfo = new CacheInfo { KeyBase = keyBase, KeyModifiers = keySuffix, ExpiresIn = Duration > 0 ? TimeSpan.FromSeconds(Duration) : (TimeSpan?)null, MaxAge = MaxAge >= 0 ? TimeSpan.FromSeconds(MaxAge) : (TimeSpan?)null, CacheControl = CacheControl, VaryByUser = VaryByUser, LocalCache = LocalCache, NoCompression = NoCompression, }; if (await req.HandleValidCache(cacheInfo).ConfigAwait()) return; req.Items[Keywords.CacheInfo] = cacheInfo; }
public static async Task<bool> HandleValidCache(this IRequest req, CacheInfo cacheInfo, CancellationToken token=default) { if (cacheInfo == null) return false; ICacheClient cache; ICacheClientAsync cacheAsync = null; // only non-null if native ICacheClientAsync exists if (cacheInfo.LocalCache) cache = HostContext.AppHost.GetMemoryCacheClient(req); else HostContext.AppHost.TryGetNativeCacheClient(req, out cache, out cacheAsync); var cacheControl = HostContext.GetPlugin<HttpCacheFeature>().BuildCacheControlHeader(cacheInfo); var res = req.Response; DateTime? lastModified = null; var doHttpCaching = cacheInfo.MaxAge != null || cacheInfo.CacheControl != CacheControl.None; if (doHttpCaching) { lastModified = cacheAsync != null ? await cacheAsync.GetAsync<DateTime?>(cacheInfo.LastModifiedKey(), token).ConfigAwait() : cache.Get<DateTime?>(cacheInfo.LastModifiedKey()); if (req.HasValidCache(lastModified)) { if (cacheControl != null) res.AddHeader(HttpHeaders.CacheControl, cacheControl); res.EndNotModified(); return true; } } var encoding = !cacheInfo.NoCompression ? req.GetCompressionType() : null; var useCacheKey = encoding != null ? cacheInfo.CacheKey + "." + encoding : cacheInfo.CacheKey; var responseBytes = cacheAsync != null ? await cacheAsync.GetAsync<byte[]>(useCacheKey, token).ConfigAwait() : cache.Get<byte[]>(useCacheKey); if (responseBytes != null) { if (encoding != null) res.AddHeader(HttpHeaders.ContentEncoding, encoding); if (cacheInfo.VaryByUser) res.AddHeader(HttpHeaders.Vary, "Cookie"); if (cacheControl != null) res.AddHeader(HttpHeaders.CacheControl, cacheControl); if (!doHttpCaching) { lastModified = cacheAsync != null ? await cacheAsync.GetAsync<DateTime?>(cacheInfo.LastModifiedKey(), token).ConfigAwait() : cache.Get<DateTime?>(cacheInfo.LastModifiedKey()); } if (lastModified != null) res.AddHeader(HttpHeaders.LastModified, lastModified.Value.ToUniversalTime().ToString("r")); await res.WriteBytesToResponse(responseBytes, req.ResponseContentType, token).ConfigAwait(); return true; } return false; }
public EventSubscription(IResponse response) { this.response = response; this.Meta = new Dictionary <string, string>(); this.WriteEvent = HostContext.GetPlugin <ServerEventsFeature>().WriteEvent; }
public static object Cache(this ICacheClient cacheClient, string cacheKey, object responseDto, IRequest request, TimeSpan?expireCacheIn = null) { request.Response.Dto = responseDto; cacheClient.Set(cacheKey, responseDto, expireCacheIn); if (!request.ResponseContentType.IsBinary()) { string serializedDto = SerializeToString(request, responseDto); string modifiers = null; if (request.ResponseContentType.MatchesContentType(MimeTypes.Json)) { var jsonp = request.GetJsonpCallback(); if (jsonp != null) { modifiers = ".jsonp," + jsonp.SafeVarName(); serializedDto = jsonp + "(" + serializedDto + ")"; //Add a default expire timespan for jsonp requests, //because they aren't cleared when calling ClearCaches() if (expireCacheIn == null) { expireCacheIn = HostContext.Config.DefaultJsonpCacheExpiration; } } } var cacheKeySerialized = GetCacheKeyForSerialized(cacheKey, request.ResponseContentType, modifiers); cacheClient.Set(cacheKeySerialized, serializedDto, expireCacheIn); var compressionType = request.GetCompressionType(); bool doCompression = compressionType != null; if (doCompression) { var lastModified = HostContext.GetPlugin <HttpCacheFeature>().ShouldAddLastModifiedToOptimizedResults() && request.Response.GetHeader(HttpHeaders.CacheControl) == null ? DateTime.UtcNow : (DateTime?)null; var cacheKeySerializedZip = GetCacheKeyForCompressed(cacheKeySerialized, compressionType); byte[] compressedSerializedDto = serializedDto.Compress(compressionType); cacheClient.Set(cacheKeySerializedZip, compressedSerializedDto, expireCacheIn); if (lastModified != null) { cacheClient.Set(DateCacheKey(cacheKeySerializedZip), lastModified.Value.Ticks, expireCacheIn); } return(compressedSerializedDto != null ? new CompressedResult(compressedSerializedDto, compressionType, request.ResponseContentType) { Status = request.Response.StatusCode, LastModified = lastModified, } : null); } return(serializedDto); } else { string modifiers = null; byte[] serializedDto = HostContext.ContentTypes.SerializeToBytes(request, responseDto); var cacheKeySerialized = GetCacheKeyForSerialized(cacheKey, request.ResponseContentType, modifiers); cacheClient.Set(cacheKeySerialized, serializedDto, expireCacheIn); return(serializedDto); } }
public override void Execute(IRequest req, IResponse res, object requestDto) { if (req.Verb != HttpMethods.Get && req.Verb != HttpMethods.Head) { return; } var feature = HostContext.GetPlugin <HttpCacheFeature>(); if (feature == null) { throw new NotSupportedException(ErrorMessages.CacheFeatureMustBeEnabled.Fmt("[CacheResponse]")); } var keyBase = "res:" + req.RawUrl; var keySuffix = MimeTypes.GetExtension(req.ResponseContentType); var modifiers = ""; if (req.ResponseContentType == MimeTypes.Json) { string jsonp = req.GetJsonpCallback(); if (jsonp != null) { modifiers = "jsonp:" + jsonp.SafeVarName(); } } if (VaryByUser) { modifiers += (modifiers.Length > 0 ? "+" : "") + "user:"******"+" : "") + "role:" + role; } } } } if (modifiers.Length > 0) { keySuffix += "+" + modifiers; } var cacheInfo = new CacheInfo { KeyBase = keyBase, KeyModifiers = keySuffix, ExpiresIn = Duration > 0 ? TimeSpan.FromSeconds(Duration) : (TimeSpan?)null, MaxAge = MaxAge >= 0 ? TimeSpan.FromSeconds(MaxAge) : (TimeSpan?)null, CacheControl = CacheControl, VaryByUser = VaryByUser, LocalCache = LocalCache, }; if (req.HandleValidCache(cacheInfo)) { return; } req.Items[Keywords.CacheInfo] = cacheInfo; }
public static bool HandleValidCache(this IRequest req, CacheInfo cacheInfo) { if (cacheInfo == null) { return(false); } var res = req.Response; var cache = cacheInfo.LocalCache ? HostContext.LocalCache : HostContext.Cache; DateTime?lastModified = null; var doHttpCaching = cacheInfo.MaxAge != null || cacheInfo.CacheControl != CacheControl.None; if (doHttpCaching) { lastModified = cache.Get <DateTime?>(cacheInfo.LastModifiedKey()); if (req.HasValidCache(lastModified)) { res.EndNotModified(); return(true); } } var encoding = req.GetCompressionType(); var responseBytes = encoding != null ? cache.Get <byte[]>(cacheInfo.CacheKey + "." + encoding) : cache.Get <byte[]>(cacheInfo.CacheKey); if (responseBytes != null) { if (encoding != null) { res.AddHeader(HttpHeaders.ContentEncoding, encoding); } if (cacheInfo.VaryByUser) { res.AddHeader(HttpHeaders.Vary, "Cookie"); } var cacheControl = HostContext.GetPlugin <HttpCacheFeature>().BuildCacheControlHeader(cacheInfo); if (cacheControl != null) { res.AddHeader(HttpHeaders.CacheControl, cacheControl); } if (!doHttpCaching) { lastModified = cache.Get <DateTime?>(cacheInfo.LastModifiedKey()); } if (lastModified != null) { res.AddHeader(HttpHeaders.LastModified, lastModified.Value.ToUniversalTime().ToString("r")); } res.WriteBytesToResponse(responseBytes, req.ResponseContentType); return(true); } return(false); }