如何使.Net web-API能够接受g-zipedpost

我有一个相当重要的标准.net MVC 4 Web API应用程序。

public class LogsController : ApiController { public HttpResponseMessage PostLog(List logs) { if (logs != null && logs.Any()) { var goodLogs = new List(); var badLogs = new List(); foreach (var logDto in logs) { if (logDto.IsValid()) { goodLogs.Add(logDto.ToLog()); } else { badLogs.Add(logDto.ToLogBad()); } } if (goodLogs.Any()) { _logsRepo.Save(goodLogs); } if(badLogs.Any()) { _logsBadRepo.Save(badLogs); } } return new HttpResponseMessage(HttpStatusCode.OK); } } 

这一切都很好,我有能够发送他们的日志的设备,它运作良好。 但是现在我们开始担心要传输的数据的大小,我们想看一下接受使用GZIP压缩的post?

我该怎么做呢? 是在IIS中设置还是我可以使用Action Filters?

编辑1

跟着菲利普的回答,我的想法是我需要在它到达我的控制器之前拦截请求的处理。 如果我可以在Web api框架尝试将请求的主体解析到我的业务对象之前捕获请求,这会因为请求的主体仍然被压缩而失败。 然后我可以解压缩请求的主体,然后将请求传递回处理链,希望Web Api框架能够将(解压缩的)主体解析为我的业务对象。

它看起来像使用DelagatingHandler是要走的路。 它允许我在处理期间访问请求,但在我的控制器之前。 所以我尝试了下面这个?

  public class gZipHandler : DelegatingHandler { protected override System.Threading.Tasks.Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { string encodingType = request.Headers.AcceptEncoding.First().Value; request.Content = new DeCompressedContent(request.Content, encodingType); return base.SendAsync(request, cancellationToken); } } public class DeCompressedContent : HttpContent { private HttpContent originalContent; private string encodingType; public DeCompressedContent(HttpContent content, string encodType) { originalContent = content; encodingType = encodType; } protected override bool TryComputeLength(out long length) { length = -1; return false; } protected override Task CreateContentReadStreamAsync() { return base.CreateContentReadStreamAsync(); } protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) { Stream compressedStream = null; if (encodingType == "gzip") { compressedStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: true); } return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk => { if (compressedStream != null) { compressedStream.Dispose(); } }); } } 

}

这似乎工作正常。 在我的控制器和调用DecompressedContent的构造函数之前调用SendAsync方法。 但是从不调用SerializeToStreamAsync,因此我添加了CreateContentReadStreamAsync,以查看是否应该进行解压缩,但是也没有调用。

我觉得我接近解决方案,但只需要一点点额外就可以了解它。

我有同样的要求将gzip压缩数据发送到.NET web api控制器。 我提出了这个解决方案:

 public class GZipToJsonHandler : DelegatingHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Handle only if content type is 'application/gzip' if (request.Content.Headers.ContentType == null || request.Content.Headers.ContentType.MediaType != "application/gzip") { return base.SendAsync(request, cancellationToken); } // Read in the input stream, then decompress in to the outputstream. // Doing this asynronously, but not really required at this point // since we end up waiting on it right after this. Stream outputStream = new MemoryStream(); Task task = request.Content.ReadAsStreamAsync().ContinueWith(t => { Stream inputStream = t.Result; var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); gzipStream.CopyTo(outputStream); gzipStream.Dispose(); outputStream.Seek(0, SeekOrigin.Begin); }); // Wait for inputstream and decompression to complete. Would be nice // to not block here and work async when ready instead, but I couldn't // figure out how to do it in context of a DelegatingHandler. task.Wait(); // This next section is the key... // Save the original content HttpContent origContent = request.Content; // Replace request content with the newly decompressed stream request.Content = new StreamContent(outputStream); // Copy all headers from original content in to new one foreach (var header in origContent.Headers) { request.Content.Headers.Add(header.Key, header.Value); } // Replace the original content-type with content type // of decompressed data. In our case, we can assume application/json. A // more generic and reuseable handler would need some other // way to differentiate the decompressed content type. request.Content.Headers.Remove("Content-Type"); request.Content.Headers.Add("Content-Type", "application/json"); return base.SendAsync(request, cancellationToken); } } 

使用此方法,现有的控制器(通常使用JSON内容和自动模型绑定)继续工作而不进行任何更改。

我不确定为什么其他答案被接受了。 它提供了处理响应的解决方案(这是常见的),但不是请求(这是不常见的)。 Accept-Encoding标头用于指定可接受的响应编码,与请求编码无关。

我相信正确的答案是Kaliatech的,我会留下这个评论并投票他的我有足够的声望点,因为我认为他基本上是正确的。

但是,我的情况要求需要查看编码类型类型而不是内容类型。 使用这种方法,调用系统仍然可以指定内容类型中的内容类型是json / xml / etc,但是指定使用gzip或可能的其他编码/压缩机制对数据进行编码。 这使我无需在解码输入后更改内容类型,并允许任何内容类型信息以其原始状态流过。

这是代码。 同样,其中99%是Kaliatech的答案,包括评论,所以如果有用的话,请将他的post投票。

 public class CompressedRequestHandler : DelegatingHandler { protected override System.Threading.Tasks.Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { if (IsRequetCompressed(request)) { request.Content = DecompressRequestContent(request); } return base.SendAsync(request, cancellationToken); } private bool IsRequetCompressed(HttpRequestMessage request) { if (request.Content.Headers.ContentEncoding != null && request.Content.Headers.ContentEncoding.Contains("gzip")) { return true; } return false; } private HttpContent DecompressRequestContent(HttpRequestMessage request) { // Read in the input stream, then decompress in to the outputstream. // Doing this asynronously, but not really required at this point // since we end up waiting on it right after this. Stream outputStream = new MemoryStream(); Task task = request.Content.ReadAsStreamAsync().ContinueWith(t => { Stream inputStream = t.Result; var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); gzipStream.CopyTo(outputStream); gzipStream.Dispose(); outputStream.Seek(0, SeekOrigin.Begin); }); // Wait for inputstream and decompression to complete. Would be nice // to not block here and work async when ready instead, but I couldn't // figure out how to do it in context of a DelegatingHandler. task.Wait(); // Save the original content HttpContent origContent = request.Content; // Replace request content with the newly decompressed stream HttpContent newContent = new StreamContent(outputStream); // Copy all headers from original content in to new one foreach (var header in origContent.Headers) { newContent.Headers.Add(header.Key, header.Value); } return newContent; } 

然后我在全球注册了这个处理程序,如果你容易受到DoS攻击,这可能是一个冒险的主张,但是我们的服务被锁定了,所以它适用于我们

 GlobalConfiguration.Configuration.MessageHandlers.Add(new CompressedRequestHandler()); 

虽然Web API不支持开箱即用的Accept-Encoding标头,但Kiran有一篇关于如何做到这一点的博客文章 – http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04 /handling-compression-accept-encoding-sample.aspx – 使用自定义MessageHandler

如果您实现他的解决方案,您需要做的就是使用Accept-Encoding: gzip发出请求Accept-Encoding: gzipAccept-Encoding: deflate标头,Web API响应将在消息处理程序中为您压缩。

试试这个

  public class DeCompressedContent : HttpContent { private HttpContent originalContent; private string encodingType; ///  /// ///  ///  ///  public DeCompressedContent(HttpContent content, string encodingType) { if (content == null) throw new ArgumentNullException("content"); if (string.IsNullOrWhiteSpace(encodingType)) throw new ArgumentNullException("encodingType"); this.originalContent = content; this.encodingType = encodingType.ToLowerInvariant(); if (!this.encodingType.Equals("gzip", StringComparison.CurrentCultureIgnoreCase) && !this.encodingType.Equals("deflate", StringComparison.CurrentCultureIgnoreCase)) { throw new InvalidOperationException(string.Format("Encoding {0} is not supported. Only supports gzip or deflate encoding", this.encodingType)); } foreach (KeyValuePair> header in originalContent.Headers) { this.Headers.TryAddWithoutValidation(header.Key, header.Value); } this.Headers.ContentEncoding.Add(this.encodingType); } ///  /// ///  ///  ///  ///  protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) { var output = new MemoryStream(); return this.originalContent .CopyToAsync(output).ContinueWith(task => { // go to start output.Seek(0, SeekOrigin.Begin); if (this.encodingType.Equals("gzip", StringComparison.CurrentCultureIgnoreCase)) { using (var dec = new GZipStream(output, CompressionMode.Decompress)) { dec.CopyTo(stream); } } else { using (var def = new DeflateStream(output, CompressionMode.Decompress)) { def.CopyTo(stream); } } if (output != null) output.Dispose(); }); } ///  /// ///  ///  ///  protected override bool TryComputeLength(out long length) { length = -1; return (false); } }