策略以及如何在.net core 3.0中间件中创建请求和响应管道

时间:2019-04-08 06:34:26

标签: c# asp.net .net asp.net-web-api asp.net-core

我正在开发.net核心中间件(api),并考虑使用遵循以下流程的管道,有人可以告诉我这是一个好的方法,并遵循最佳实践,还是应该使用其他策略。

  1. 请求进入api
  2. 授权管道验证请求。
  3. 请求管道将请求记录到db中。
  4. 请求转到api并执行操作并返回结果。
  5. 响应管道获取响应并登录到db,然后将结果返回给客户端。

我知道我们只能读取流时间(第3点),但是我已经知道了这一点,并且在读取后我将其附加到请求流上。

那么,混淆在哪里写响应?在api中?或在单独的管道中。

如果我在单独的管道中执行此操作,那么我将处理两次响应(一次是在api中创建响应,第二次是在单独的管道中读取响应),这对性能造成了影响。

我可以将数据从第4点传送到第5点,例如从api传递到我的管道,并从那里将响应添加到响应流中吗?如果正确,那么如何将数据从api传递到管道?

1 个答案:

答案 0 :(得分:0)

是的,响应流只能读取一次。您可以使用MemoryStream来加载响应reference article

  • 首先,读取请求并将其格式化为字符串。

  • 接下来,创建一个虚拟MemoryStream来将新响应加载到其中。

  • 然后,等待服务器返回响应。

  • 最后,将虚拟MemoryStream(包含实际响应)复制到原始流中,然后将其返回给客户端。

代码示例:

public class RequestResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestResponseLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        //First, get the incoming request
        var request = await FormatRequest(context.Request);

        //Copy a pointer to the original response body stream
        var originalBodyStream = context.Response.Body;

        //Create a new memory stream...
        using (var responseBody = new MemoryStream())
        {
            //...and use that for the temporary response body
            context.Response.Body = responseBody;

            //Continue down the Middleware pipeline, eventually returning to this class
            await _next(context);

            //Format the response from the server
            var response = await FormatResponse(context.Response);

            //TODO: Save log to chosen datastore

            //Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client.
            await responseBody.CopyToAsync(originalBodyStream);
        }
    }

    private async Task<string> FormatRequest(HttpRequest request)
    {
        var body = request.Body;

        //This line allows us to set the reader for the request back at the beginning of its stream.
        request.EnableRewind();

        //We now need to read the request stream.  First, we create a new byte[] with the same length as the request stream...
        var buffer = new byte[Convert.ToInt32(request.ContentLength)];

        //...Then we copy the entire request stream into the new buffer.
        await request.Body.ReadAsync(buffer, 0, buffer.Length);

        //We convert the byte[] into a string using UTF8 encoding...
        var bodyAsText = Encoding.UTF8.GetString(buffer);

        //..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
        request.Body = body;

        return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
    }

    private async Task<string> FormatResponse(HttpResponse response)
    {
        //We need to read the response stream from the beginning...
        response.Body.Seek(0, SeekOrigin.Begin);

        //...and copy it into a string
        string text = await new StreamReader(response.Body).ReadToEndAsync();

        //We need to reset the reader for the response so that the client can read it.
        response.Body.Seek(0, SeekOrigin.Begin);

        //Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
        return $"{response.StatusCode}: {text}";
    }
}

并注册中间件:

app.UseMiddleware<RequestResponseLoggingMiddleware>();