public void ReplyWithFileContent(string path, string originalPath = null) { //Deal with BL-3153, where the file was still open in another thread FileStream fs; if (!RobustFile.Exists(path)) { //for audio, at least, this is not really an error. We constantly are asking if audio already exists for the current segment //enhance: maybe audio should go through a different path, e.g. "/bloom/audio/somefile.wav" //then this path COULD write and error //Logger.WriteError("Server could not find" + path); _actualContext.Response.StatusCode = 404; return; } try { fs = RobustFile.OpenRead(path); } catch (Exception error) { Logger.WriteError("Server could not read " + path, error); _actualContext.Response.StatusCode = 500; return; } using (fs) { _actualContext.Response.ContentLength64 = fs.Length; _actualContext.Response.AppendHeader("PathOnDisk", HttpUtility.UrlEncode(path)); if (ShouldCache(path, originalPath)) { _actualContext.Response.AppendHeader("Cache-Control", "max-age=600000"); // about a week...if someone spends longer editing one book, well, files will get loaded one more time... } // A HEAD request (rather than a GET or POST request) is a request for just headers, and nothing can be written // to the OutputStream. It is normally used to check if the contents of the file have changed without taking the // time and bandwidth needed to download the full contents of the file. The 2 pieces of information being returned // are the Content-Length and Last-Modified headers. The requestor can use this information to determine if the // contents of the file have changed, and if they have changed the requestor can then decide if the file needs to // be reloaded. It is useful when debugging with tools which automatically reload the page when something changes. if (_actualContext.Request.HttpMethod == "HEAD") { var lastModified = RobustFile.GetLastWriteTimeUtc(path).ToString("R"); // Originally we were returning the Last-Modified header with every response, but we discovered that this was // causing Geckofx to cache the contents of the files. This made debugging difficult because, even if the file // changed, Geckofx would use the cached file rather than requesting the updated file from the localhost. _actualContext.Response.AppendHeader("Last-Modified", lastModified); } else if (fs.Length < 2 * 1024 * 1024) { // This buffer size was picked to be big enough for any of the standard files we load in every page. // Profiling indicates it is MUCH faster to use Response.Close() rather than writing to the output stream, // though the gain may be illusory since the final 'false' argument allows our code to proceed without waiting // for the complete data transfer. At a minimum, it makes this thread available to work on another // request sooner. var buffer = new byte[fs.Length]; fs.Read(buffer, 0, (int)fs.Length); _actualContext.Response.Close(buffer, false); } else { // For really big (typically image) files, use the old buffered approach try { var buffer = new byte[1024 * 512]; //512KB int read; while ((read = fs.Read(buffer, 0, buffer.Length)) > 0) { _actualContext.Response.OutputStream.Write(buffer, 0, read); } _actualContext.Response.OutputStream.Close(); } catch (HttpListenerException e) { // If the page is gone and no longer able to accept the data, just log it. ReportHttpListenerProblem(e); } } } HaveOutput = true; }
public void ReplyWithFileContent(string path) { //Deal with BL-3153, where the file was still open in another thread FileStream fs; if (!RobustFile.Exists(path)) { //for audio, at least, this is not really an error. We constantly are asking if audio already exists for the current segment //enhance: maybe audio should go through a different path, e.g. "/bloom/audio/somefile.wav" //then this path COULD write and error //Logger.WriteError("Server could not find" + path); _actualContext.Response.StatusCode = 404; return; } try { fs = RobustFile.OpenRead(path); } catch (Exception error) { Logger.WriteError("Server could not read " + path, error); _actualContext.Response.StatusCode = 500; return; } using (fs) { _actualContext.Response.ContentLength64 = fs.Length; _actualContext.Response.AppendHeader("PathOnDisk", HttpUtility.UrlEncode(path)); //helps with debugging what file is being chosen // A HEAD request (rather than a GET or POST request) is a request for just headers, and nothing can be written // to the OutputStream. It is normally used to check if the contents of the file have changed without taking the // time and bandwidth needed to download the full contents of the file. The 2 pieces of information being returned // are the Content-Length and Last-Modified headers. The requestor can use this information to determine if the // contents of the file have changed, and if they have changed the requestor can then decide if the file needs to // be reloaded. It is useful when debugging with tools which automatically reload the page when something changes. if (_actualContext.Request.HttpMethod == "HEAD") { var lastModified = RobustFile.GetLastWriteTimeUtc(path).ToString("R"); // Originally we were returning the Last-Modified header with every response, but we discovered that this was // causing Geckofx to cache the contents of the files. This made debugging difficult because, even if the file // changed, Geckofx would use the cached file rather than requesting the updated file from the localhost. _actualContext.Response.AppendHeader("Last-Modified", lastModified); } else { var buffer = new byte[1024 * 512]; //512KB int read; while ((read = fs.Read(buffer, 0, buffer.Length)) > 0) { _actualContext.Response.OutputStream.Write(buffer, 0, read); } } } _actualContext.Response.OutputStream.Close(); HaveOutput = true; }