From c035f2baa1f3537d298a6559d15bd7ca40188e5d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 12 Nov 2016 01:58:50 -0500 Subject: update responses --- .../HttpServer/HttpListenerHost.cs | 17 +++- .../HttpServer/HttpResultFactory.cs | 91 ++++++++-------------- .../SocketSharp/WebSocketSharpResponse.cs | 7 -- .../HttpServer/StreamWriter.cs | 12 +-- 4 files changed, 54 insertions(+), 73 deletions(-) (limited to 'Emby.Server.Implementations/HttpServer') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 64f498b12..49c664eec 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -86,9 +86,7 @@ namespace Emby.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - public override void Configure() - { - var mapExceptionToStatusCode = new Dictionary + readonly Dictionary _mapExceptionToStatusCode = new Dictionary { {typeof (InvalidOperationException), 500}, {typeof (NotImplementedException), 500}, @@ -102,6 +100,8 @@ namespace Emby.Server.Implementations.HttpServer {typeof (NotSupportedException), 500} }; + public override void Configure() + { var requestFilters = _appHost.GetExports().ToList(); foreach (var filter in requestFilters) { @@ -240,7 +240,12 @@ namespace Emby.Server.Implementations.HttpServer return; } - httpRes.StatusCode = 500; + int statusCode; + if (!_mapExceptionToStatusCode.TryGetValue(ex.GetType(), out statusCode)) + { + statusCode = 500; + } + httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; httpRes.Write(ex.Message); @@ -518,6 +523,10 @@ namespace Emby.Server.Implementations.HttpServer { await handler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false); } + else + { + ErrorHandler(new FileNotFoundException(), httpReq); + } } catch (Exception ex) { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index bbd556661..f65331ec7 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -34,19 +34,16 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private readonly IXmlSerializer _xmlSerializer; + private readonly IMemoryStreamFactory _memoryStreamFactory; /// /// Initializes a new instance of the class. /// - /// The log manager. - /// The file system. - /// The json serializer. - public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer) + public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMemoryStreamFactory memoryStreamFactory) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; - _xmlSerializer = xmlSerializer; + _memoryStreamFactory = memoryStreamFactory; _logger = logManager.GetLogger("HttpResultFactory"); } @@ -59,17 +56,13 @@ namespace Emby.Server.Implementations.HttpServer /// System.Object. public object GetResult(object content, string contentType, IDictionary responseHeaders = null) { - return GetHttpResult(content, contentType, responseHeaders); + return GetHttpResult(content, contentType, true, responseHeaders); } /// /// Gets the HTTP result. /// - /// The content. - /// Type of the content. - /// The response headers. - /// IHasHeaders. - private IHasHeaders GetHttpResult(object content, string contentType, IDictionary responseHeaders = null) + private IHasHeaders GetHttpResult(object content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null) { IHasHeaders result; @@ -98,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer } else { - result = new HttpResult(content, contentType); + result = new HttpResult(content, contentType, HttpStatusCode.OK); } } } @@ -107,7 +100,11 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - responseHeaders["Expires"] = "-1"; + if (addCachePrevention) + { + responseHeaders["Expires"] = "-1"; + } + AddResponseHeaders(result, responseHeaders); return result; @@ -184,8 +181,6 @@ namespace Emby.Server.Implementations.HttpServer /// public object ToOptimizedResult(IRequest request, T dto) { - request.Response.Dto = dto; - var compressionType = GetCompressionType(request); if (compressionType == null) { @@ -204,6 +199,7 @@ namespace Emby.Server.Implementations.HttpServer } } + // Do not use the memoryStreamFactory here, they don't place nice with compression using (var ms = new MemoryStream()) { using (var compressionStream = GetCompressionStream(ms, compressionType)) @@ -213,12 +209,9 @@ namespace Emby.Server.Implementations.HttpServer var compressedBytes = ms.ToArray(); - var httpResult = new HttpResult(compressedBytes, request.ResponseContentType) - { - Status = request.Response.StatusCode - }; + var httpResult = new StreamWriter(compressedBytes, request.ResponseContentType, _logger); - httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); + //httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); httpResult.Headers["Content-Encoding"] = compressionType; return httpResult; @@ -226,6 +219,16 @@ namespace Emby.Server.Implementations.HttpServer } } + private static Stream GetCompressionStream(Stream outputStream, string compressionType) + { + if (compressionType == "deflate") + return new DeflateStream(outputStream, CompressionMode.Compress, true); + if (compressionType == "gzip") + return new GZipStream(outputStream, CompressionMode.Compress, true); + + throw new NotSupportedException(compressionType); + } + public static string GetRealContentType(string contentType) { return contentType == null @@ -233,7 +236,7 @@ namespace Emby.Server.Implementations.HttpServer : contentType.Split(';')[0].ToLower().Trim(); } - public static string SerializeToXmlString(object from) + private string SerializeToXmlString(object from) { using (var ms = new MemoryStream()) { @@ -253,16 +256,6 @@ namespace Emby.Server.Implementations.HttpServer } } - private static Stream GetCompressionStream(Stream outputStream, string compressionType) - { - if (compressionType == "deflate") - return new DeflateStream(outputStream, CompressionMode.Compress); - if (compressionType == "gzip") - return new GZipStream(outputStream, CompressionMode.Compress); - - throw new NotSupportedException(compressionType); - } - /// /// Gets the optimized result using cache. /// @@ -358,23 +351,7 @@ namespace Emby.Server.Implementations.HttpServer return hasHeaders; } - IHasHeaders httpResult; - - var stream = result as Stream; - - if (stream != null) - { - httpResult = new StreamWriter(stream, contentType, _logger); - } - else - { - // Otherwise wrap into an HttpResult - httpResult = new HttpResult(result, contentType ?? "text/html", HttpStatusCode.NotModified); - } - - AddResponseHeaders(httpResult, responseHeaders); - - return httpResult; + return GetHttpResult(result, contentType, false, responseHeaders); } /// @@ -603,7 +580,7 @@ namespace Emby.Server.Implementations.HttpServer { stream.Dispose(); - return GetHttpResult(new byte[] { }, contentType); + return GetHttpResult(new byte[] { }, contentType, true); } return new StreamWriter(stream, contentType, _logger) @@ -630,13 +607,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - return GetHttpResult(new byte[] { }, contentType); + return GetHttpResult(new byte[] { }, contentType, true); } - return GetHttpResult(contents, contentType, responseHeaders); + return GetHttpResult(contents, contentType, true, responseHeaders); } - public static byte[] Compress(string text, string compressionType) + private byte[] Compress(string text, string compressionType) { if (compressionType == "deflate") return Deflate(text); @@ -647,12 +624,12 @@ namespace Emby.Server.Implementations.HttpServer throw new NotSupportedException(compressionType); } - public static byte[] Deflate(string text) + private byte[] Deflate(string text) { return Deflate(Encoding.UTF8.GetBytes(text)); } - public static byte[] Deflate(byte[] bytes) + private byte[] Deflate(byte[] bytes) { // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream @@ -666,12 +643,12 @@ namespace Emby.Server.Implementations.HttpServer } } - public static byte[] GZip(string text) + private byte[] GZip(string text) { return GZip(Encoding.UTF8.GetBytes(text)); } - public static byte[] GZip(byte[] buffer) + private byte[] GZip(byte[] buffer) { using (var ms = new MemoryStream()) using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index de0b33fe3..9de86e9cc 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -77,8 +77,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return _response.OutputStream; } } - public object Dto { get; set; } - public void Write(string text) { var bOutput = System.Text.Encoding.UTF8.GetBytes(text); @@ -120,11 +118,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } } - public void End() - { - Close(); - } - public void Flush() { _response.OutputStream.Flush(); diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 15488abaa..33378949c 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -25,6 +25,8 @@ namespace Emby.Server.Implementations.HttpServer /// The source stream. private Stream SourceStream { get; set; } + private byte[] SourceBytes { get; set; } + /// /// The _options /// @@ -40,7 +42,6 @@ namespace Emby.Server.Implementations.HttpServer public Action OnComplete { get; set; } public Action OnError { get; set; } - private readonly byte[] _bytes; /// /// Initializes a new instance of the class. @@ -73,14 +74,13 @@ namespace Emby.Server.Implementations.HttpServer /// Type of the content. /// The logger. public StreamWriter(byte[] source, string contentType, ILogger logger) - : this(new MemoryStream(source), contentType, logger) { if (string.IsNullOrEmpty(contentType)) { throw new ArgumentNullException("contentType"); } - _bytes = source; + SourceBytes = source; Logger = logger; Headers["Content-Type"] = contentType; @@ -92,9 +92,11 @@ namespace Emby.Server.Implementations.HttpServer { try { - if (_bytes != null) + var bytes = SourceBytes; + + if (bytes != null) { - await responseStream.WriteAsync(_bytes, 0, _bytes.Length); + await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); } else { -- cgit v1.2.3 From a855864207fe3ed0ac9b4d648617bb1cb39df3f3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 12 Nov 2016 02:14:04 -0500 Subject: limit access to response stream --- .../HttpServer/HttpListenerHost.cs | 22 +++++++++++++++------- .../SocketSharp/WebSocketSharpResponse.cs | 21 ++++----------------- MediaBrowser.Model/Services/IRequest.cs | 13 ------------- ServiceStack/Host/ContentTypes.cs | 10 +--------- ServiceStack/HttpResponseExtensionsInternal.cs | 22 ++++++++++++---------- 5 files changed, 32 insertions(+), 56 deletions(-) (limited to 'Emby.Server.Implementations/HttpServer') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 49c664eec..41b7a4622 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; @@ -248,9 +249,7 @@ namespace Emby.Server.Implementations.HttpServer httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; - httpRes.Write(ex.Message); - - httpRes.Close(); + Write(httpRes, ex.Message); } catch { @@ -404,7 +403,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; - httpRes.Write("Invalid host"); + Write(httpRes, "Invalid host"); return; } @@ -458,7 +457,7 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - httpRes.Write( + Write(httpRes, "EmbyPlease update your Emby bookmark to " + newUrl + ""); return; @@ -475,7 +474,7 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - httpRes.Write( + Write(httpRes, "EmbyPlease update your Emby bookmark to " + newUrl + ""); return; @@ -513,7 +512,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.ContentType = "text/html"; - httpRes.Write(GlobalResponse); + Write(httpRes, GlobalResponse); return; } @@ -547,6 +546,15 @@ namespace Emby.Server.Implementations.HttpServer } } + private void Write(IResponse response, string text) + { + var bOutput = Encoding.UTF8.GetBytes(text); + response.SetContentLength(bOutput.Length); + + var outputStream = response.OutputStream; + outputStream.Write(bOutput, 0, bOutput.Length); + } + public static void RedirectToUrl(IResponse httpRes, string url) { httpRes.StatusCode = 302; diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 9de86e9cc..a8b115056 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -77,16 +77,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return _response.OutputStream; } } - public void Write(string text) - { - var bOutput = System.Text.Encoding.UTF8.GetBytes(text); - _response.ContentLength64 = bOutput.Length; - - var outputStream = _response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); - Close(); - } - public void Close() { if (!this.IsClosed) @@ -108,8 +98,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp { try { - response.OutputStream.Flush(); - response.OutputStream.Dispose(); + var outputStream = response.OutputStream; + + outputStream.Flush(); + outputStream.Dispose(); response.Close(); } catch (Exception ex) @@ -118,11 +110,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } } - public void Flush() - { - _response.OutputStream.Flush(); - } - public bool IsClosed { get; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 455a69d37..e9a9f1c5b 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -136,23 +136,12 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } - /// - /// Write once to the Response Stream then close it. - /// - /// - void Write(string text); - /// /// Signal that this response has been handled and no more processing should be done. /// When used in a request or response filter, no more filters or processing is done on this request. /// void Close(); - /// - /// Response.Flush() and OutputStream.Flush() seem to have different behaviour in ASP.NET - /// - void Flush(); - /// /// Gets a value indicating whether this instance is closed. /// @@ -160,8 +149,6 @@ namespace MediaBrowser.Model.Services void SetContentLength(long contentLength); - bool KeepAlive { get; set; } - //Add Metadata to Response Dictionary Items { get; } } diff --git a/ServiceStack/Host/ContentTypes.cs b/ServiceStack/Host/ContentTypes.cs index 58ba29801..8840e7c8b 100644 --- a/ServiceStack/Host/ContentTypes.cs +++ b/ServiceStack/Host/ContentTypes.cs @@ -18,15 +18,7 @@ namespace ServiceStack.Host serializer(response, responseStream); } - public Action GetResponseSerializer(string contentType) - { - var serializer = GetStreamSerializer(contentType); - if (serializer == null) return null; - - return (dto, httpRes) => serializer(dto, httpRes.OutputStream); - } - - public Action GetStreamSerializer(string contentType) + private Action GetStreamSerializer(string contentType) { switch (GetRealContentType(contentType)) { diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs index 88b82bdf6..44b790f5f 100644 --- a/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -6,6 +6,7 @@ using System.IO; using System.Net; using System.Threading.Tasks; using System.Collections.Generic; +using System.Text; using System.Threading; using MediaBrowser.Model.Services; using ServiceStack.Host; @@ -14,19 +15,19 @@ namespace ServiceStack { public static class HttpResponseExtensionsInternal { - public static async Task WriteToOutputStream(IResponse response, object result) + public static async Task WriteToOutputStream(IResponse response, Stream outputStream, object result) { var asyncStreamWriter = result as IAsyncStreamWriter; if (asyncStreamWriter != null) { - await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); + await asyncStreamWriter.WriteToAsync(outputStream, CancellationToken.None).ConfigureAwait(false); return true; } var streamWriter = result as IStreamWriter; if (streamWriter != null) { - streamWriter.WriteTo(response.OutputStream); + streamWriter.WriteTo(outputStream); return true; } @@ -35,7 +36,7 @@ namespace ServiceStack { using (stream) { - await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); + await stream.CopyToAsync(outputStream).ConfigureAwait(false); return true; } } @@ -46,7 +47,7 @@ namespace ServiceStack response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await outputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return true; } @@ -151,10 +152,11 @@ namespace ServiceStack response.ContentType += "; charset=utf-8"; } - var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); + var outputStream = response.OutputStream; + + var writeToOutputStreamResult = await WriteToOutputStream(response, outputStream, result).ConfigureAwait(false); if (writeToOutputStreamResult) { - response.Flush(); //required for Compression return; } @@ -164,12 +166,12 @@ namespace ServiceStack if (response.ContentType == null || response.ContentType == "text/html") response.ContentType = defaultContentType; - response.Write(responseText); + var bytes = Encoding.UTF8.GetBytes(responseText); + await outputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; } - var serializer = ContentTypes.Instance.GetResponseSerializer(defaultContentType); - serializer(result, response); + ContentTypes.Instance.SerializeToStream(request, result, outputStream); } } } -- cgit v1.2.3