Modify middleware response

前端 未结 4 1906
粉色の甜心
粉色の甜心 2020-12-06 00:30

My requirement: write a middleware that filters all \"bad words\" out of a response that comes from another subsequent middleware (e.g. Mvc).

The problem: streaming

相关标签:
4条回答
  • 2020-12-06 01:09

    A simpler version based on the code I used:

    /// <summary>
    /// The middleware Invoke method.
    /// </summary>
    /// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
    /// <returns>A Task to support async calls.</returns>
    public async Task Invoke(HttpContext httpContext)
    {
        var originBody = httpContext.Response.Body;
        try
        {
            var memStream = new MemoryStream();
            httpContext.Response.Body = memStream;
    
            await _next(httpContext).ConfigureAwait(false);
    
            memStream.Position = 0;
            var responseBody = new StreamReader(memStream).ReadToEnd();
    
            //Custom logic to modify response
            responseBody = responseBody.Replace("hello", "hi", StringComparison.InvariantCultureIgnoreCase);
    
            var memoryStreamModified = new MemoryStream();
            var sw = new StreamWriter(memoryStreamModified);
            sw.Write(responseBody);
            sw.Flush();
            memoryStreamModified.Position = 0;
    
            await memoryStreamModified.CopyToAsync(originBody).ConfigureAwait(false);
        }
        finally
        {
            httpContext.Response.Body = originBody;
        }
    }
    
    0 讨论(0)
  • 2020-12-06 01:17

    Replace a response stream to MemoryStream to prevent its sending. Return the original stream after the response is modified:

        public async Task Invoke(HttpContext context)
        {
            bool modifyResponse = true;
            Stream originBody = null;
    
            if (modifyResponse)
            {
                //uncomment this line only if you need to read context.Request.Body stream
                //context.Request.EnableRewind();
    
                originBody = ReplaceBody(context.Response);
            }
    
            await _next(context);
    
            if (modifyResponse)
            {
                //as we replaced the Response.Body with a MemoryStream instance before,
                //here we can read/write Response.Body
                //containing the data written by middlewares down the pipeline 
    
                //finally, write modified data to originBody and set it back as Response.Body value
                ReturnBody(context.Response, originBody);
            }
        }
    
        private Stream ReplaceBody(HttpResponse response)
        {
            var originBody = response.Body;
            response.Body = new MemoryStream();
            return originBody;
        }
    
        private void ReturnBody(HttpResponse response, Stream originBody)
        {
            response.Body.Seek(0, SeekOrigin.Begin);
            response.Body.CopyTo(originBody);
            response.Body = originBody;
        }
    

    It's a workaround and it can cause performance problems. I hope to see a better solution here.

    0 讨论(0)
  • 2020-12-06 01:27

    Unfortunately I'm not allowed to comment since my score is too low. So just wanted to post my extension of the excellent top solution, and a modification for .NET Core 3.0+

    First of all

    context.Request.EnableRewind();
    

    has been changed to

    context.Request.EnableBuffering();
    

    in .NET Core 3.0+

    And here's how I read/write the body content:

    First a filter, so we just modify the content types we're interested in

    private static readonly IEnumerable<string> validContentTypes = new HashSet<string>() { "text/html", "application/json", "application/javascript" };
    

    It's a solution for transforming nuggeted texts like [[[Translate me]]] into its translation. This way I can just mark up everything that needs to be translated, read the po-file we've gotten from the translator, and then do the translation replacement in the output stream - regardless if the nuggeted texts is in a razor view, javascript or ... whatever. Kind of like the TurquoiseOwl i18n package does, but in .NET Core, which that excellent package unfortunately doesn't support.

    ...
    
    if (modifyResponse)
    {
        //as we replaced the Response.Body with a MemoryStream instance before,
        //here we can read/write Response.Body
        //containing the data written by middlewares down the pipeline
    
        var contentType = context.Response.ContentType?.ToLower();
        contentType = contentType?.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();   // Filter out text/html from "text/html; charset=utf-8"
    
        if (validContentTypes.Contains(contentType))
        {
            using (var streamReader = new StreamReader(context.Response.Body))
            {
                // Read the body
                context.Response.Body.Seek(0, SeekOrigin.Begin);
                var responseBody = await streamReader.ReadToEndAsync();
    
                // Replace [[[Bananas]]] with translated texts - or Bananas if a translation is missing
                responseBody = NuggetReplacer.ReplaceNuggets(poCatalog, responseBody);
    
                // Create a new stream with the modified body, and reset the content length to match the new stream
                var requestContent = new StringContent(responseBody, Encoding.UTF8, contentType);
                context.Response.Body = await requestContent.ReadAsStreamAsync();//modified stream
                context.Response.ContentLength = context.Response.Body.Length;
            }
        }
    
        //finally, write modified data to originBody and set it back as Response.Body value
        ReturnBody(context.Response, originBody);
    }
    ...
    
    private void ReturnBody(HttpResponse response, Stream originBody)
    {
        response.Body.Seek(0, SeekOrigin.Begin);
        response.Body.CopyToAsync(originBody);
        response.Body = originBody;
    }
    
    0 讨论(0)
  • 2020-12-06 01:33

    A "real" production scenario may be found here: tethys logging middeware

    If you follow the logic presented in the link, do not forget to addhttpContext.Request.EnableRewind() prior calling _next(httpContext) (extension method of Microsoft.AspNetCore.Http.Internal namespace).

    0 讨论(0)
提交回复
热议问题