在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
并实现BeginRequest
和EndRequest
事件。
所有的“原始”数据在HttpRequest
和HttpResponse
之间,只是不是一个原始格式。 以下是构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
似乎是要走的路。
看看httpworkerrequest
, readentitybody
和GetPreloadedEntityBody
。
要获得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理方式。 虽然这将是一个痛苦。