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

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

我不确定的是如何以最less的“错位”方式来获得这些数据。 我可以通过检查HttpRequest对象的所有属性并从它们构build一个string(以及类似的响应)来重新构成我认为请求看起来像的内容,但是我真的想要得到实际的请求/响应数据这是在电线上发送的。

我很高兴使用任何拦截机制,如filter,模块等,解决scheme可以特定于IIS7。 但是,我宁愿只保留在托pipe代码中。

任何build议?

编辑:我注意到, HttpRequest有一个SaveAs方法,可以将请求保存到磁盘,但这使用内部帮助器方法的负载重build内部状态的请求,不能公开访问(为什么这不允许保存到用户提供的stream我不知道)。 所以它开始看起来像我将尽我所能重build从对象的请求/响应文本…呻吟。

编辑2:请注意,我说的整个请求,包括方法,path,标题等。目前的反应只看在身体stream不包括这个信息。

编辑3:没有人在这里读问题吗? 目前为止有五个答案,但还没有人提供一个获得整个原始的在线请求的方法。 是的,我知道我可以从请求对象中捕获输出stream,头文件和URL以及所有这些东西。 我已经说过,在这个问题上,请看:

我可以通过检查HttpRequest对象的所有属性并从它们构build一个string(以及类似的响应)来重新构成我认为请求看起来像的内容,但是我真的想要得到实际的请求/响应数据这是在电线上发送的。

如果您知道完整的原始数据(包括标题,url,http方法等),则无法检索,那么知道这些数据就很有用。 同样的,如果你知道如何以原始格式(是的,我还是指包括头文件,url,http方法等等)来完成这个工作,而不需要重新构build它,那么这就是非常有用的。 但是告诉我,我可以从HttpRequest / HttpResponse对象重build它是没有用的。 我知道。 我已经说过了。


请注意:在任何人开始说这是一个坏主意之前,或者会限制可伸缩性等,我们也将在分布式环境中实现节stream,顺序传输和反重放机制,因此无论如何都需要数据库日志logging。 我不想讨论这是否是一个好主意,我正在寻找如何做到这一点。

肯定使用IHttpModule并实现BeginRequestEndRequest事件。

所有的“原始”数据在HttpRequestHttpResponse之间,只是不是一个原始格式。 以下是构buildFiddler风格转储所需的部分(与获取的原始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" 

请注意, 您无法读取响应stream,因此必须将筛选器添加到输出stream并捕获副本。

在你的BeginRequest ,你需要添加一个响应filter:

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

存储filter ,您可以在EndRequest处理程序中find它。 我build议在HttpContext.Items 。 然后可以在filter.ReadStream()获得完整的响应数据。

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

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

下面的HttpRequest扩展方法将创build一个可以粘贴到提琴手并重播的string。

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

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

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

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

那么,我正在做一个项目,可能不是太深,使用请求参数的日志:

看一看:

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

你可以装饰你的控制器类来完全logging它:

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

或者只logging一些个别的操作方法

 [Log] public ActionResult LoggedAction(){...} 

任何理由你需要保持在托pipe代码?

值得一提的是,如果您不喜欢重新发明轮子,则可以在IIS7中启用“ 失败的跟踪”日志logging 。 这将logging标题,请求和响应正文以及许多其他事情。

失败的跟踪记录

我和麦克麦的方法一起去了。 这是我写的一个模块,可以让你开始,希望能为你节省一些时间。 您需要明确地使用适合您的东西来连接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 } } 

好吧,看起来答案是“不,你不能得到原始数据,你必须从parsing对象的属性重build请求/响应”。 哦,我已经做了重build的事情。

使用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)); } } 

如果偶尔使用,绕过一个狭窄的angular落,怎么样的东西像下面的原油?

 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 

HttpRequest和HttpResponse pre MVC以前有一个GetInputStream()和GetOutputStream()可以用于这个目的。 没有看看MVC的那些部分,所以林不知道他们是有用的,但可能是一个想法:)

我同意其他人,使用IHttpModule。 看看这个问题的答案,这个问题几乎和你所问的一样。 它logging了请求和响应,但没有标题。

如何跟踪ScriptService WebService请求?

在你的应用程序之外完成这可能是最好的。 你可以设置一个反向代理来做这样的事情(还有更多)。 反向代理基本上是一个位于服务器机房的Web服务器,位于Web服务器和客户机之间。 见http://en.wikipedia.org/wiki/Reverse_proxy

同意FigmentEngine, IHttpModule似乎是要走的路。

看看httpworkerrequestreadentitybodyGetPreloadedEntityBody

要获得httpworkerrequest你需要这样做:

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

inApp是httpapplication对象。

您可以在DelegatingHandler中使用Stream.CopyToAsync()函数在.NET 4.5中的其他答案中提到的OutputFilter中完成此操作。

我不确定详细信息,但是当您尝试直接读取响应stream时,它不会触发所有不好的事情。

例:

 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. } } 

我知道这不是托pipe代码,但我会build议一个ISAPIfilter。 自从我拥有维护自己的ISAPI的“快感”已经有两年了,但是从我记得你可以访问所有这些东西,在ASP.Net之前和之后都做到了这一点。

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

如果一个HTTPModule对于你所需要的不够好,那么我不认为在所需的细节上有任何pipe理方式。 虽然这将是一个痛苦。