Compare commits

...

3 Commits

Author SHA1 Message Date
Taloth Saldono 40b34dbb5e Do custom gzip compression on mono when returning api responses to avoid a memory leak. 2019-01-08 20:34:35 +01:00
Taloth Saldono 567719e6c4 Only do the custom decompression on mono. 2019-01-08 20:33:50 +01:00
Taloth Saldono 76212ef908 Do our own decompression to workaround mono bug. 2019-01-04 23:29:32 +01:00
2 changed files with 54 additions and 13 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Net; using System.Net;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -25,10 +26,19 @@ namespace NzbDrone.Common.Http.Dispatchers
{ {
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url); var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
if (PlatformInfo.IsMono)
{
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
webRequest.AutomaticDecompression = DecompressionMethods.None;
webRequest.Headers.Add("Accept-Encoding", "gzip");
}
else
{
// Deflate is not a standard and could break depending on implementation. // Deflate is not a standard and could break depending on implementation.
// we should just stick with the more compatible Gzip // we should just stick with the more compatible Gzip
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net //http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.GZip; webRequest.AutomaticDecompression = DecompressionMethods.GZip;
}
webRequest.Method = request.Method.ToString(); webRequest.Method = request.Method.ToString();
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent); webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
@ -36,6 +46,7 @@ namespace NzbDrone.Common.Http.Dispatchers
webRequest.AllowAutoRedirect = false; webRequest.AllowAutoRedirect = false;
webRequest.CookieContainer = cookies; webRequest.CookieContainer = cookies;
if (request.RequestTimeout != TimeSpan.Zero) if (request.RequestTimeout != TimeSpan.Zero)
{ {
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds); webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
@ -107,6 +118,19 @@ namespace NzbDrone.Common.Http.Dispatchers
try try
{ {
data = responseStream.ToBytes(); data = responseStream.ToBytes();
if (PlatformInfo.IsMono && httpWebResponse.ContentEncoding == "gzip")
{
using (var compressedStream = new MemoryStream(data))
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var decompressedStream = new MemoryStream())
{
gzip.CopyTo(decompressedStream);
data = decompressedStream.ToArray();
}
httpWebResponse.Headers.Remove("Content-Encoding");
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -15,9 +15,14 @@ namespace Sonarr.Http.Extensions.Pipelines
public int Order => 0; public int Order => 0;
private readonly Action<Action<Stream>, Stream> _writeGZipStream;
public GzipCompressionPipeline(Logger logger) public GzipCompressionPipeline(Logger logger)
{ {
_logger = logger; _logger = logger;
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
_writeGZipStream = NzbDrone.Common.EnvironmentInfo.PlatformInfo.IsMono ? WriteGZipStreamMono : (Action<Action<Stream>, Stream>)WriteGZipStream;
} }
public void Register(IPipelines pipelines) public void Register(IPipelines pipelines)
@ -43,14 +48,7 @@ namespace Sonarr.Http.Extensions.Pipelines
var contents = response.Contents; var contents = response.Contents;
response.Headers["Content-Encoding"] = "gzip"; response.Headers["Content-Encoding"] = "gzip";
response.Contents = responseStream => response.Contents = responseStream => _writeGZipStream(contents, responseStream);
{
using (var gzip = new GZipStream(responseStream, CompressionMode.Compress, true))
using (var buffered = new BufferedStream(gzip, 8192))
{
contents.Invoke(buffered);
}
};
} }
} }
@ -61,6 +59,25 @@ namespace Sonarr.Http.Extensions.Pipelines
} }
} }
private static void WriteGZipStreamMono(Action<Stream> innerContent, Stream targetStream)
{
using (var membuffer = new MemoryStream())
{
WriteGZipStream(innerContent, membuffer);
membuffer.Position = 0;
membuffer.CopyTo(targetStream);
}
}
private static void WriteGZipStream(Action<Stream> innerContent, Stream targetStream)
{
using (var gzip = new GZipStream(targetStream, CompressionMode.Compress, true))
using (var buffered = new BufferedStream(gzip, 8192))
{
innerContent.Invoke(buffered);
}
}
private static bool ContentLengthIsTooSmall(Response response) private static bool ContentLengthIsTooSmall(Response response)
{ {
var contentLength = response.Headers.GetValueOrDefault("Content-Length"); var contentLength = response.Headers.GetValueOrDefault("Content-Length");