代码之家  ›  专栏  ›  技术社区  ›  Greg Beech

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

  •  129
  • Greg Beech  · 技术社区  · 16 年前

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

    我不确定的是如何以最少的“损坏”方式获得这些数据。我可以通过检查 HttpRequest 对象并从中构建一个字符串(对于响应也是如此),但我真的很想获得通过网络发送的实际请求/响应数据。

    有什么建议吗?

    编辑: HttpRequest 有一个 SaveAs 方法,该方法可以将请求保存到磁盘,但它使用大量无法公开访问的内部助手方法从内部状态重新构造请求(这就是为什么不允许保存到用户提供的流,我不知道)。所以看起来我必须尽全力从对象中重建请求/响应文本。。。呻吟

    编辑2: 请注意,我说的是 整体

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

    如果你知道 原始数据(包括标题、url、http方法等)根本无法检索,因此了解这些信息将非常有用。类似地,如果您知道如何以原始格式(是的,我的意思仍然是包括头、url、http方法等)获取所有内容,而不必重新构建它,这就是我所要求的,那么这将非常有用。但是告诉我我可以从 HttpRequest HttpResponse 对象没有用处。我知道。我已经说过了。


    请注意:在任何人开始说这是个坏主意或限制可伸缩性等之前,我们还将在分布式环境中实现节流、顺序传递和反重放机制,因此无论如何都需要数据库日志记录。我不是想讨论这是否是一个好主意,而是想知道如何做到这一点。

    15 回复  |  直到 16 年前
        1
  •  92
  •   Andrew Savinykh    11 年前

    一定要用 IHttpModule BeginRequest EndRequest 事件。

    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
    

    "HTTP/1.1 " + response.Status
    response.Headers // loop through these "key: value"
    

    注意 因此,您必须向输出流添加一个过滤器并捕获一个副本。

    ,则需要添加响应筛选器:

    HttpResponse response = HttpContext.Current.Response;
    OutputFilterStream filter = new OutputFilterStream(response.Filter);
    response.Filter = filter;
    

    百货商店 filter 结束请求 处理程序。我建议加入 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);
        }
    }
    
        2
  •  52
  •   Dan Atkinson    11 年前

    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;
                }
            }
        }
    }
    
        3
  •  35
  •   Vincent de Lagabbe    15 年前

    string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];
    

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

        4
  •  16
  •   John Prado    15 年前

    嗯,我在做一个项目,用请求参数做了一个日志,可能不太深:

    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();
        }
    

    您可以装饰controllers类以完全记录它:

    [Log]
    public class TermoController : Controller {...}
    

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

    [Log]
    public ActionResult LoggedAction(){...}
    
        5
  •  15
  •   JoelBellot    11 年前

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

    值得一提的是,您可以启用 Failed Trace logging 在IIS7中,如果你不喜欢重新发明轮子。这将记录头、请求和响应主体以及许多其他内容。

    Failed Trace Logging

        6
  •  9
  •   groksrc    13 年前

    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
        }
    }
    
        7
  •  6
  •   Greg Beech    15 年前

    好的,看来答案是“不,你不能得到原始数据,你必须根据解析对象的属性重新构造请求/响应”。哦,好吧,我已经做了重建工作。

        8
  •  3
  •   FigmentEngine    16 年前

    使用 IHttpModule :

        namespace Intercepts
    {
        class Interceptor : IHttpModule
        {
            private readonly InterceptorEngine engine = new InterceptorEngine();
    
            #region IHttpModule Members
    
            void IHttpModule.Dispose()
            {
            }
    
            void IHttpModule.Init(HttpApplication application)
            {
                application.EndRequest += new EventHandler(engine.Application_EndRequest);
            }
            #endregion
        }
    }
    
        class InterceptorEngine
        {       
            internal void Application_EndRequest(object sender, EventArgs e)
            {
                HttpApplication application = (HttpApplication)sender;
    
                HttpResponse response = application.Context.Response;
                ProcessResponse(response.OutputStream);
            }
    
            private void ProcessResponse(Stream stream)
            {
                Log("Hello");
                StreamReader sr = new StreamReader(stream);
                string content = sr.ReadToEnd();
                Log(content);
            }
    
            private void Log(string line)
            {
                Debugger.Log(0, null, String.Format("{0}\n", line));
            }
        }
    
        9
  •  3
  •   Baburaj    11 年前

    如果只是偶尔使用,为了避开困境,下面这种粗糙的东西怎么样?

    Public Function GetRawRequest() As String
        Dim str As String = ""
        Dim path As String = "C:\Temp\REQUEST_STREAM\A.txt"
        System.Web.HttpContext.Current.Request.SaveAs(path, True)
        str = System.IO.File.ReadAllText(path)
        Return str
    End Function
    
        10
  •  1
  •   Talonj    10 年前

    DelegatingHandler 不使用 OutputFilter Stream.CopyToAsync() 作用

    我不确定细节,但它不会触发当您试图直接读取响应流时发生的所有不好的事情。

    public class LoggingHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            DoLoggingWithRequest(request);
            var response = await base.SendAsync(request, cancellationToken);
            await DoLoggingWithResponse(response);
            return response;
        }
    
        private async Task DologgingWithResponse(HttpResponseMessage response) {
            var stream = new MemoryStream();
            await response.Content.CopyToAsync(stream).ConfigureAwait(false);     
            DoLoggingWithResponseContent(Encoding.UTF8.GetString(stream.ToArray()));
    
            // The rest of this call, the implementation of the above method, 
            // and DoLoggingWithRequest is left as an exercise for the reader.
        }
    }
    
        11
  •  0
  •   Chris    16 年前

    我知道这不是托管代码,但我建议使用ISAPI过滤器。我有幸维护自己的ISAPI已经有好几年了,但据我回忆,你可以访问所有这些东西,无论是在ASP.Net完成它之前还是之后。

    http://msdn.microsoft.com/en-us/library/ms524610.aspx

    如果一个HTTPModule不能满足您的需要,那么我认为没有任何一种管理方法可以做到这一点。但这会很痛苦。

        12
  •  0
  •   Community CDub    8 年前

    我同意其他人的看法,使用IHTTP模块。看看这个问题的答案,它的作用几乎与你所问的相同。它记录请求和响应,但不记录头。

    How to trace ScriptService WebService requests?

        13
  •  0
  •   Lance Fisher    15 年前

    最好在应用程序之外执行此操作。您可以设置一个反向代理来执行类似的操作(以及更多操作)。反向代理基本上是位于服务器室内的web服务器,位于web服务器和客户端之间。看见 http://en.wikipedia.org/wiki/Reverse_proxy

        14
  •  0
  •   adrianbanks    11 年前

    同意虚构的引擎, IHttpModule 这似乎是一条路要走。

    调查 httpworkerrequest , readentitybody GetPreloadedEntityBody

    得到 httpworkerrequest 您需要这样做:

    (HttpWorkerRequest)inApp.Context.GetType().GetProperty("WorkerRequest", bindingFlags).GetValue(inApp.Context, null);
    

    inApp 是httpapplication对象。

        15
  •  0
  •   Roman Marusyk S Kotra    7 年前

    HttpRequest HttpResponse GetInputStream() GetOutputStream() 这可以用于这一目的。还没有研究MVC中的这些部分,所以我不确定它们是否可用,但可能是一个想法:)