2012-09-10 6 views
13

私はかなり標準の.net MVC 4 Web APIアプリケーションを持っています。g-ziped投稿を受け入れるために.Net web-APIを有効にするには

public class LogsController : ApiController 
{ 

    public HttpResponseMessage PostLog(List<LogDto> logs) 
    { 
     if (logs != null && logs.Any()) 
     { 
      var goodLogs = new List<Log>(); 
      var badLogs = new List<LogBad>(); 

      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を使用して圧縮された投稿を受け入れることを検討したいのですか?

どうすればいいですか?それはIISで設定されているのでしょうか、またはユーザーのアクションフィルターを使用できますか?

EDIT 1

私の考えに答えるフィリップさんからのフォローアップは、私はそれが私のコントローラに到達する前にリクエストの処理をインターセプトする必要があるということです。リクエストの本文がまだ圧縮されているため、Web APIフレームワークがリクエストの本文をビジネスオブジェクトに解析しようとする前にリクエストを捕捉できれば失敗します。次に、リクエストの本文を解凍し、要求を処理チェーンに戻して、Web APIフレームワークが(解凍された)本文をビジネスオブジェクトに解析できるようにします。

DelagatingHandlerを使用するように見えます。これは、処理中ではなく、コントローラの前に要求にアクセスできるようにします。だから私は次のことを試みた?

public class gZipHandler : DelegatingHandler 
{ 

    protected override System.Threading.Tasks.Task<HttpResponseMessage> 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<Stream> 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(); 
      } 
     }); 
    } 



} 

}

これは、[OK]を動作しているようです。私のコントローラとDecompressedContentのコンストラクタが呼び出される前に、SendAsyncメソッドが呼び出されています。しかし、SerializeToStreamAsyncが呼び出されることはありませんので、CreateContentReadStreamAsyncを追加して、解凍する必要があるかどうかを確認しましたが、どちらも呼び出されていません。

私は解決策に近づいているようになりましたが、ライン上でそれを取得するにはちょっと必要があります。

+0

クライアント側でJSONデータをどのように圧縮しましたか?ありがとう。 –

答えて

21

.NET Web APIコントローラにgzippedデータをPOSTするのと同じ要件がありました。私はこの解決策を考え出した:

public class GZipToJsonHandler : DelegatingHandler 
{ 
    protected override Task<HttpResponseMessage> 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ヘッダーは、受け入れ可能な応答エンコードを指定するために使用され、要求エンコードには関係しません。

+0

)に基づいて作成されたオープンソースライブラリが実際にありますOutputStreamでDisposeを呼び出すには? はいの場合、いつですか? ありがとう –

+0

outputStreamはMemoryStreamなので、ストリームを明示的に破棄または閉じる必要はありません。詳細については、この回答を参照してください:http://stackoverflow.com/questions/4274590/memorystream-close-or-memorystream-dispose – kaliatech

+4

カスタムエンコーディングではなく、アプリケーションエンコーディングを使用する必要があります。 Content-Typeは変更しないでください。 – Softlion

6

のWeb APIは、箱から出しAccept-Encodingヘッダーをサポートしていませんが、キランはそれを行う方法についての素晴らしいブログの記事がありますが - http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling-compression-accept-encoding-sample.aspx - カスタムMessageHandlerのに

を使用して、あなたが彼のソリューションを実装する場合は、あなたが必要なすべてをするには、Accept-Encoding: gzipまたはAccept-Encoding: deflateヘッダーでリクエストを発行し、Web APIレスポンスがメッセージハンドラで圧縮されます。

+2

クール、ありがとう。それは役に立ちました。私のシナリオは少し異なります。私は(まだ)私のWeb APIからの応答を圧縮するのではなく、すでに圧縮された要求(投稿)を受け入れることに関心がありません。私はこれらの投稿の本文を解凍し、その中のデータを処理する必要があります。私の次のステップについては、問題の編集を参照してください。ありがとうございます –

+0

実際にあなたが言及した記事や他のブログ記事(https://github.com/azzlack/Microsoft.AspNet.WebApi.MessageHandlers.Compression – fretje

20

私は正しい答えがKaliatechのものだと信じています。私は彼が基本的に正しいと思うので、私は十分な評判ポイントを持っています。

しかし私の状況では、コンテンツタイプではなくエンコードタイプタイプを調べる必要がありました。この方法を使用すると、呼び出し側システムはコンテンツタイプがコンテンツタイプでjson/xml/etcであることを指定できますが、gzipまたは潜在的に別のエンコーディング/圧縮メカニズムを使用してデータがエンコードされるように指定します。これにより、入力を解読した後にコンテンツタイプを変更する必要がなくなり、コンテンツタイプ情報が元の状態で流れるようになりました。

ここにコードがあります。ここでも、99%はKaliatechのコメントを含む答えなので、便利な場合は投稿に投票してください。

public class CompressedRequestHandler : DelegatingHandler 
{ 
    protected override System.Threading.Tasks.Task<HttpResponseMessage> 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攻撃に対して脆弱である場合dicey命題することができた、世界的にこのハンドラを登録しますが、私たちは

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

これはなぜDoS攻撃の脆弱性を露呈するのか説明すると非常に興味があります。なぜなら、どんな要求でもかなりのCPU使用率が上がるからですか? –

0

この

を試すためにそれが動作するように、私たちのサービスは、ロックダウンされています
public class DeCompressedContent : HttpContent 
{ 
    private HttpContent originalContent; 
    private string encodingType; 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="content"></param> 
    /// <param name="encodingType"></param> 
    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<string, IEnumerable<string>> header in originalContent.Headers) 
     { 
      this.Headers.TryAddWithoutValidation(header.Key, header.Value); 
     } 

     this.Headers.ContentEncoding.Add(this.encodingType); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="stream"></param> 
    /// <param name="context"></param> 
    /// <returns></returns> 
    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(); 
      }); 


    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="length"></param> 
    /// <returns></returns> 
    protected override bool TryComputeLength(out long length) 
    { 
     length = -1; 

     return (false); 
    } 
} 
関連する問題