/// <summary> /// The transmit entity strategy to use. /// </summary> /// <param name="fileEntityCacheItem">The cache item.</param> /// <returns></returns> public ITransmitEntityStrategy GetTransmitEntityStrategy(FileEntityCacheItem fileEntityCacheItem) { if (fileEntityCacheItem.EntityData == null) { //Let IIS send file content with TransmitFile return(new TransmitEntityStrategyForIIS(fileEntityCacheItem, FileInfo.FullName)); } else { //We will serve the in memory file return(new TransmitEntityStrategyForByteArray(fileEntityCacheItem, fileEntityCacheItem.EntityData)); } }
/// <summary> /// Get a fileHanderCacheItem for the requested file. /// </summary> /// <param name="entityStoredWithCompressionType">The compression type to use for the file.</param> /// <param name="fileEntityCacheItem">The fileHandlerCacheItem </param> /// <returns>Returns true if a fileHandlerCacheItem can be created; otherwise false.</returns> public bool TryGetFileHandlerCacheItem(ResponseCompressionType entityStoredWithCompressionType, out FileEntityCacheItem fileEntityCacheItem) { fileEntityCacheItem = null; // If the response bytes are already cached, then deliver the bytes directly from cache var cacheKey = FileInfoEntityType + ":" + entityStoredWithCompressionType + ":" + FileInfo.FullName; var cachedValue = HttpRuntime.Cache.Get(cacheKey); if (cachedValue != null) { fileEntityCacheItem = (FileEntityCacheItem)cachedValue; } else { //File does not exist if (!FileInfo.Exists) { return false; } //File too large to send if (FileInfo.Length > MaxFileSizeToServe) { return false; } var etag = string.Empty; var lastModifiedFileTime = FileInfo.LastWriteTime.ToUniversalTime(); //When a browser sets the If-Modified-Since field to 13-1-2010 10:30:58, another DateTime instance is created, but this one has a Ticks value of 633989754580000000 //But the time from the file system is accurate to a tick. So it might be 633989754586086250. var lastModified = new DateTime(lastModifiedFileTime.Year, lastModifiedFileTime.Month, lastModifiedFileTime.Day, lastModifiedFileTime.Hour, lastModifiedFileTime.Minute, lastModifiedFileTime.Second); var contentType = MimeTyper.GetMimeType(FileInfo.Extension); var contentLength = FileInfo.Length; //ETAG is always calculated from uncompressed entity data switch (FileEntitySetting.EtagMethod) { case EtagMethodType.MD5: etag = Hasher.CalculateMd5Etag(FileInfo); break; case EtagMethodType.LastModified: etag = lastModified.ToString(); break; default: throw new Exception("Unknown etag method generation"); } fileEntityCacheItem = new FileEntityCacheItem { Etag = etag, LastModified = lastModified, ContentLength = contentLength, ContentType = contentType, CompressionType = entityStoredWithCompressionType }; if (FileEntitySetting.ServeFromMemory && (contentLength <= FileEntitySetting.MaxMemorySize)) { // When not compressed, buffer is the size of the file but when compressed, // initial buffer size is one third of the file size. Assuming, compression // will give us less than 1/3rd of the size using (var memoryStream = new MemoryStream( entityStoredWithCompressionType == ResponseCompressionType.None ? Convert.ToInt32(FileInfo.Length) : Convert.ToInt32((double)FileInfo.Length / 3))) { GetEntityData(entityStoredWithCompressionType, memoryStream); var entityData = memoryStream.ToArray(); var entityDataLength = entityData.LongLength; fileEntityCacheItem.EntityData = entityData; fileEntityCacheItem.ContentLength = entityDataLength; } } //Put fileHandlerCacheItem into cache with 30 min sliding expiration, also if file changes then remove fileHandlerCacheItem from cache HttpRuntime.Cache.Insert( cacheKey, fileEntityCacheItem, new CacheDependency(FileInfo.FullName), Cache.NoAbsoluteExpiration, FileEntitySetting.MemorySlidingExpiration, CacheItemPriority.BelowNormal, null); } return true; }
/// <summary> /// The transmit entity strategy to use. /// </summary> /// <param name="fileEntityCacheItem">The cache item.</param> /// <returns></returns> public ITransmitEntityStrategy GetTransmitEntityStrategy(FileEntityCacheItem fileEntityCacheItem) { if (fileEntityCacheItem.EntityData == null) { //Let IIS send file content with TransmitFile return new TransmitEntityStrategyForIIS(fileEntityCacheItem, FileInfo.FullName); } else { //We will serve the in memory file return new TransmitEntityStrategyForByteArray(fileEntityCacheItem, fileEntityCacheItem.EntityData); } }
/// <summary> /// Get a fileHanderCacheItem for the requested file. /// </summary> /// <param name="entityStoredWithCompressionType">The compression type to use for the file.</param> /// <param name="fileEntityCacheItem">The fileHandlerCacheItem </param> /// <returns>Returns true if a fileHandlerCacheItem can be created; otherwise false.</returns> public bool TryGetFileHandlerCacheItem(ResponseCompressionType entityStoredWithCompressionType, out FileEntityCacheItem fileEntityCacheItem) { fileEntityCacheItem = null; // If the response bytes are already cached, then deliver the bytes directly from cache var cacheKey = FileInfoEntityType + ":" + entityStoredWithCompressionType + ":" + FileInfo.FullName; var cachedValue = CacheManager.Get <FileEntityCacheItem>(cacheKey); if (cachedValue != null) { fileEntityCacheItem = cachedValue; } else { //File does not exist if (!FileInfo.Exists) { return(false); } //File too large to send if (FileInfo.Length > MaxFileSizeToServe) { return(false); } var etag = string.Empty; var lastModifiedFileTime = FileInfo.LastWriteTime.ToUniversalTime(); //When a browser sets the If-Modified-Since field to 13-1-2010 10:30:58, another DateTime instance is created, but this one has a Ticks value of 633989754580000000 //But the time from the file system is accurate to a tick. So it might be 633989754586086250. var lastModified = new DateTime(lastModifiedFileTime.Year, lastModifiedFileTime.Month, lastModifiedFileTime.Day, lastModifiedFileTime.Hour, lastModifiedFileTime.Minute, lastModifiedFileTime.Second); var contentType = MimeTyper.GetMimeType(FileInfo.Extension); var contentLength = FileInfo.Length; //ETAG is always calculated from uncompressed entity data switch (FileEntitySetting.EtagMethod) { case EtagMethodType.MD5: etag = Hasher.CalculateMd5Etag(FileInfo); break; case EtagMethodType.LastModified: etag = lastModified.ToString(); break; default: throw new Exception("Unknown etag method generation"); } fileEntityCacheItem = new FileEntityCacheItem { Etag = etag, LastModified = lastModified, ContentLength = contentLength, ContentType = contentType, CompressionType = entityStoredWithCompressionType }; if (FileEntitySetting.ServeFromMemory && (contentLength <= FileEntitySetting.MaxMemorySize)) { // When not compressed, buffer is the size of the file but when compressed, // initial buffer size is one third of the file size. Assuming, compression // will give us less than 1/3rd of the size using (var memoryStream = new MemoryStream( entityStoredWithCompressionType == ResponseCompressionType.None ? Convert.ToInt32(FileInfo.Length) : Convert.ToInt32((double)FileInfo.Length / 3))) { GetEntityData(entityStoredWithCompressionType, memoryStream); var entityData = memoryStream.ToArray(); var entityDataLength = entityData.LongLength; fileEntityCacheItem.EntityData = entityData; fileEntityCacheItem.ContentLength = entityDataLength; } } //Put fileHandlerCacheItem into cache with 30 min sliding expiration, also if file changes then remove fileHandlerCacheItem from cache CacheManager.Insert( cacheKey, fileEntityCacheItem, new CacheDependency(FileInfo.FullName), Cache.NoAbsoluteExpiration, FileEntitySetting.MemorySlidingExpiration, CacheItemPriority.BelowNormal, null); } return(true); }
public void ServeRequest(HttpRequestBase request, HttpResponseBase response, FileEntity fileEntity) { if (!IsHttpMethodAllowed(request)) { //If we are unable to parse url send 405 Method not allowed HttpResponseHeaderHelper.SendHttpStatusHeaders(response, HttpStatusCode.MethodNotAllowed); return; } if (!fileEntity.IsAllowedToServeRequestedEntity) { //If we are unable to parse url send 403 Path Forbidden HttpResponseHeaderHelper.SendHttpStatusHeaders(response, HttpStatusCode.Forbidden); return; } var requestHttpMethod = HttpRequestHeaderHelper.GetHttpMethod(request); var compressionType = HttpRequestHeaderHelper.GetCompressionMode(request); // If this is a binary file like image, then we won't compress it. if (!fileEntity.IsCompressable) { compressionType = ResponseCompressionType.None; } // If it is a partial request we need to get bytes of orginal entity data, we will compress the byte ranges returned var entityStoredWithCompressionType = compressionType; var isRangeRequest = HttpRequestHeaderHelper.IsRangeRequest(request); if (isRangeRequest) { entityStoredWithCompressionType = ResponseCompressionType.None; } FileEntityCacheItem fileEntityCacheItem = null; if (!fileEntity.TryGetFileHandlerCacheItem(entityStoredWithCompressionType, out fileEntityCacheItem)) { //File does not exist if (!fileEntity.DoesEntityExists) { HttpResponseHeaderHelper.SendHttpStatusHeaders(response, HttpStatusCode.NotFound); return; } //File too large to send if (fileEntity.IsEntityLargerThanMaxFileSize) { HttpResponseHeaderHelper.SendHttpStatusHeaders(response, HttpStatusCode.RequestEntityTooLarge); return; } } else if (fileEntityCacheItem.EntityData == null && !fileEntity.DoesEntityExists) { //If we have cached the properties of the file but its to large to serve from memory then we must check that the file exists each time. HttpResponseHeaderHelper.SendHttpStatusHeaders(response, HttpStatusCode.NotFound); return; } //Unable to parse request range header IEnumerable <RangeItem> ranges = null; var requestRange = HttpRequestHeaderHelper.GetRanges(request, fileEntityCacheItem.ContentLength, out ranges); if (requestRange.HasValue && !requestRange.Value) { HttpResponseHeaderHelper.SendHttpStatusHeaders(response, HttpStatusCode.RequestedRangeNotSatisfiable); return; } //Check querystring etag var urlEtagHandler = GetUrlEtagHandler(request, response, fileEntity.UrlEtagHandlingMethod, fileEntity.UrlEtagQuerystringName, fileEntityCacheItem.Etag); if (urlEtagHandler != null) { var shouldStopResponse = urlEtagHandler.UpdateEtag(response, request.Url, fileEntityCacheItem.Etag); if (shouldStopResponse) { return; } } //Check if cached response is valid and if it is send appropriate response headers var httpStatus = GetResponseHttpStatus(request, fileEntityCacheItem.LastModified, fileEntityCacheItem.Etag); HttpResponseHeaderHelper.SendHttpStatusHeaders(response, httpStatus); if (httpStatus == HttpStatusCode.NotModified || httpStatus == HttpStatusCode.PreconditionFailed) { return; } //Tell the client it supports resumable requests HttpResponseHeaderHelper.SetResponseResumable(response); //How the entity should be cached on the client HttpResponseHeaderHelper.SetResponseCachable(response, DateTime.Now, fileEntityCacheItem.LastModified, fileEntityCacheItem.Etag, fileEntity.Expires); var entityResponseForEntity = GetEntityResponse(response, ranges); entityResponseForEntity.SendHeaders(response, compressionType, fileEntityCacheItem); var transmitEntity = fileEntity.GetTransmitEntityStrategy(fileEntityCacheItem); entityResponseForEntity.SendBody(requestHttpMethod, response, transmitEntity); }