在ASP.NET MVC和IIS7中记录原始HTTP请求/响应

Gre*_*ech 137 asp.net-mvc logging request ihttpmodule

我正在编写一个Web服务(使用ASP.NET MVC),出于支持目的,我们希望能够尽可能接近原始的在线格式(即包括HTTP)来记录请求和响应方法,路径,所有标题和正文)到数据库中.

我不确定的是如何以最少的"损坏"方式获取这些数据.通过检查HttpRequest对象的所有属性并从中构建字符串(以及类似的响应),我可以重新构建我认为请求的样子,但我真的想要获取实际的请求/响应数据.发送电线.

我很乐意使用任何拦截机制,如过滤器,模块等,解决方案可以特定于IIS7.但是,我更喜欢将其保留在托管代码中.

有什么建议?

编辑:我注意到HttpRequest有一个SaveAs方法可以将请求保存到磁盘,但是这会使用无法公开访问的内部帮助程序方法从内部状态重建请求(这就是为什么这不允许保存到用户提供的流我不知道).所以它开始看起来我将不得不尽最大努力重建来自对象的请求/响应文本......呻吟.

编辑2:请注意我说的整个请求包括方法,路径,标题等.当前响应只查看不包含此信息的正文流.

编辑3:没有人在这里阅读问题吗?到目前为止,有五个答案,但是没有一个答案暗示了一种获得整个原始线上请求的方法.是的,我知道我可以从请求对象中捕获输出流和标题以及URL和所有内容.我在问题中已经说过了,请看:

通过检查HttpRequest对象的所有属性并从中构建字符串(以及类似的响应),我可以重新构建我认为请求的样子,但我真的想得到实际的请求/响应数据这是通过电线发送的.

如果你知道完整的原始数据(包括header,url,http方法等)根本无法检索,那么知道这将是有用的.同样地,如果你知道如何以原始格式获取所有内容(是的,我仍然意味着包括标题,网址,http方法等),而不必重新构建它,这就是我所问的,那将是非常有用的.但告诉我,我可以从HttpRequest/ HttpResponse对象重建它是没有用的.我知道.我已经说过了.


请注意:在任何人开始说这是一个坏主意,或将限制可扩展性等之前,我们还将在分布式环境中实施限制,顺序传递和反重放机制,因此无论如何都需要数据库日志记录.我不是在寻找关于这是否是一个好主意的讨论,我正在寻找如何做到这一点.

mck*_*mey 89

绝对使用IHttpModule和实现BeginRequestEndRequest事件.

所有"原始"数据都存在于HttpRequest和之间HttpResponse,它只是不是单一的原始格式.以下是构建Fiddler样式转储所需的部分(尽可能接近原始HTTP):

request.HttpMethod + " " + request.RawUrl + " " + request.ServerVariables["SERVER_PROTOCOL"]
request.Headers // loop through these "key: value"
request.InputStream // make sure to reset the Position after reading or later reads may fail
Run Code Online (Sandbox Code Playgroud)

对于回复:

"HTTP/1.1 " + response.Status
response.Headers // loop through these "key: value"
Run Code Online (Sandbox Code Playgroud)

请注意,您无法读取响应流,因此您必须向输出流添加过滤器并捕获副本.

在您的工作中BeginRequest,您需要添加一个响应过滤器:

HttpResponse response = HttpContext.Current.Response;
OutputFilterStream filter = new OutputFilterStream(response.Filter);
response.Filter = filter;
Run Code Online (Sandbox Code Playgroud)

存储filterEndRequest处理程序中可以访问它的位置.我建议HttpContext.Items.然后可以获得完整的响应数据filter.ReadStream().

然后OutputFilterStream使用Decorator模式作为流的包装器实现:

/// <summary>
/// A stream which keeps an in-memory copy as it passes the bytes through
/// </summary>
public class OutputFilterStream : Stream
{
    private readonly Stream InnerStream;
    private readonly MemoryStream CopyStream;

    public OutputFilterStream(Stream inner)
    {
        this.InnerStream = inner;
        this.CopyStream = new MemoryStream();
    }

    public string ReadStream()
    {
        lock (this.InnerStream)
        {
            if (this.CopyStream.Length <= 0L ||
                !this.CopyStream.CanRead ||
                !this.CopyStream.CanSeek)
            {
                return String.Empty;
            }

            long pos = this.CopyStream.Position;
            this.CopyStream.Position = 0L;
            try
            {
                return new StreamReader(this.CopyStream).ReadToEnd();
            }
            finally
            {
                try
                {
                    this.CopyStream.Position = pos;
                }
                catch { }
            }
        }
    }


    public override bool CanRead
    {
        get { return this.InnerStream.CanRead; }
    }

    public override bool CanSeek
    {
        get { return this.InnerStream.CanSeek; }
    }

    public override bool CanWrite
    {
        get { return this.InnerStream.CanWrite; }
    }

    public override void Flush()
    {
        this.InnerStream.Flush();
    }

    public override long Length
    {
        get { return this.InnerStream.Length; }
    }

    public override long Position
    {
        get { return this.InnerStream.Position; }
        set { this.CopyStream.Position = this.InnerStream.Position = value; }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return this.InnerStream.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        this.CopyStream.Seek(offset, origin);
        return this.InnerStream.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        this.CopyStream.SetLength(value);
        this.InnerStream.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        this.CopyStream.Write(buffer, offset, count);
        this.InnerStream.Write(buffer, offset, count);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意request.RawUrl可能会触发请求验证异常.在4.5中,您可以使用request.Unvalidated.RawUrl来防止这种情况.在4.0中,我最终使用了一些反射来模仿Request.SaveAs (4认同)
  • 我认为mckamey是个天才.您是否可以为Microsoft工作,以便我们获得智能解决方案,而不是需要有出色的解决方案? (2认同)

Sam*_*les 47

HttpRequest上的以下扩展方法将创建一个可以粘贴到fiddler并重放的字符串.

namespace System.Web
{
    using System.IO;

    /// <summary>
    /// Extension methods for HTTP Request.
    /// <remarks>
    /// See the HTTP 1.1 specification http://www.w3.org/Protocols/rfc2616/rfc2616.html
    /// for details of implementation decisions.
    /// </remarks>
    /// </summary>
    public static class HttpRequestExtensions
    {
        /// <summary>
        /// Dump the raw http request to a string. 
        /// </summary>
        /// <param name="request">The <see cref="HttpRequest"/> that should be dumped.       </param>
        /// <returns>The raw HTTP request.</returns>
        public static string ToRaw(this HttpRequest request)
        {
            StringWriter writer = new StringWriter();

            WriteStartLine(request, writer);
            WriteHeaders(request, writer);
            WriteBody(request, writer);

            return writer.ToString();
        }

        private static void WriteStartLine(HttpRequest request, StringWriter writer)
        {
            const string SPACE = " ";

            writer.Write(request.HttpMethod);
            writer.Write(SPACE + request.Url);
            writer.WriteLine(SPACE + request.ServerVariables["SERVER_PROTOCOL"]);
        }

        private static void WriteHeaders(HttpRequest request, StringWriter writer)
        {
            foreach (string key in request.Headers.AllKeys)
            {
                writer.WriteLine(string.Format("{0}: {1}", key, request.Headers[key]));
            }

            writer.WriteLine();
        }

        private static void WriteBody(HttpRequest request, StringWriter writer)
        {
            StreamReader reader = new StreamReader(request.InputStream);

            try
            {
                string body = reader.ReadToEnd();
                writer.WriteLine(body);
            }
            finally
            {
                reader.BaseStream.Position = 0;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 非常好的代码!但是为了使用MVC 4,我不得不将类名改为`HttpRequestBaseExtensions`并在每个地方将`HttpRequest`改为`HttpRequestBase`. (5认同)

Vin*_*bbe 35

您可以使用ALL_RAW服务器变量来获取随请求一起发送的原始HTTP标头,然后您可以像往常一样获取InputStream:

string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];
Run Code Online (Sandbox Code Playgroud)

退房:http://msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx

  • 或者在ASP.NET服务器上下文中,使用:this.Request.ServerVariables ["ALL_RAW"]; (3认同)

Joh*_*ado 16

好吧,我正在研究一个项目并使用请求参数做了一个日志,也许不是太深了:

看一看:

public class LogAttribute : ActionFilterAttribute
{
    private void Log(string stageName, RouteData routeData, HttpContextBase httpContext)
    {
        //Use the request and route data objects to grab your data
        string userIP = httpContext.Request.UserHostAddress;
        string userName = httpContext.User.Identity.Name;
        string reqType = httpContext.Request.RequestType;
        string reqData = GetRequestData(httpContext);
        string controller = routeData["controller"];
        string action = routeData["action"];

        //TODO:Save data somewhere
    }

    //Aux method to grab request data
    private string GetRequestData(HttpContextBase context)
    {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < context.Request.QueryString.Count; i++)
        {
            sb.AppendFormat("Key={0}, Value={1}<br/>", context.Request.QueryString.Keys[i], context.Request.QueryString[i]);
        }

        for (int i = 0; i < context.Request.Form.Count; i++)
        {
            sb.AppendFormat("Key={0}, Value={1}<br/>", context.Request.Form.Keys[i], context.Request.Form[i]);
        }

        return sb.ToString();
    }
Run Code Online (Sandbox Code Playgroud)

您可以装饰控制器类以完全记录它:

[Log]
public class TermoController : Controller {...}
Run Code Online (Sandbox Code Playgroud)

或者只记录一些单独的动作方法

[Log]
public ActionResult LoggedAction(){...}
Run Code Online (Sandbox Code Playgroud)


Joe*_*lot 11

您需要将其保存在托管代码中的任何原因吗?

值得一提的是,如果您不喜欢重新发明轮子,可以在IIS7中启用失败跟踪日志记录.这会记录标头,请求和响应主体以及许多其他内容.

跟踪记录失败

  • 您也可以使用HTTP 200 OK的失败跟踪日志记录,因此仍然可以记录非故障 (6认同)
  • 这是迄今为止最简单的解决方案. (2认同)

Gro*_*Src 8

我选择了McKAMEY的方法.这是我写的一个模块,可以帮助您入门,希望能为您节省一些时间.您需要使用适合您的东西显然插入Logger:

public class CaptureTrafficModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.EndRequest += new EventHandler(context_EndRequest);
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication app = sender as HttpApplication;

        OutputFilterStream filter = new OutputFilterStream(app.Response.Filter);
        app.Response.Filter = filter;

        StringBuilder request = new StringBuilder();
        request.Append(app.Request.HttpMethod + " " + app.Request.Url);
        request.Append("\n");
        foreach (string key in app.Request.Headers.Keys)
        {
            request.Append(key);
            request.Append(": ");
            request.Append(app.Request.Headers[key]);
            request.Append("\n");
        }
        request.Append("\n");

        byte[] bytes = app.Request.BinaryRead(app.Request.ContentLength);
        if (bytes.Count() > 0)
        {
            request.Append(Encoding.ASCII.GetString(bytes));
        }
        app.Request.InputStream.Position = 0;

        Logger.Debug(request.ToString());
    }

    void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication app = sender as HttpApplication;
        Logger.Debug(((OutputFilterStream)app.Response.Filter).ReadStream());
    }

    private ILogger _logger;
    public ILogger Logger
    {
        get
        {
            if (_logger == null)
                _logger = new Log4NetLogger();
            return _logger;
        }
    }

    public void Dispose()
    {
        //Does nothing
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 您无法安全地将app.Response.Filter强制转换为Stream以外的任何内容.其他HttpModule可能会使用自己的响应过滤器包装,在这种情况下,您将获得无效的强制转换异常. (3认同)

Gre*_*ech 5

好的,所以看起来答案是"你不能得到原始数据,你必须从解析对象的属性重建请求/响应".哦,我完成了重建工作.

  • 你有没有看到Vineus对ServerVariables ["ALL_RAW"]的评论?我自己还没有尝试过,但据记录,它可以准确地返回客户端发送的原始头信息.即使文件证明是错误的,它正在进行重建,嘿,免费重建:-) (3认同)