asp.net-mvc 在 ASP.NET MVC & IIS7 中记录原始 HTTP 请求/响应
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1038466/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Logging raw HTTP request/response in ASP.NET MVC & IIS7
提问by Greg Beech
I'm writing a web service (using ASP.NET MVC) and for support purposes we'd like to be able to log the requests and response in as close as possible to the raw, on-the-wire format (i.e including HTTP method, path, all headers, and the body) into a database.
我正在编写一个 Web 服务(使用 ASP.NET MVC),出于支持目的,我们希望能够以尽可能接近原始的在线格式(即包括 HTTP方法、路径、所有标题和正文)到数据库中。
What I'm not sure of is how to get hold of this data in the least 'mangled' way. I can re-constitute what I believe the request looks like by inspecting all the properties of the HttpRequestobject and building a string from them (and similarly for the response) but I'd really like to get hold of the actual request/response data that's sent on the wire.
我不确定的是如何以最少“损坏”的方式获取这些数据。我可以通过检查HttpRequest对象的所有属性并从它们构建一个字符串(对于响应类似)来重新构建我认为请求的样子,但我真的很想掌握实际的请求/响应数据在电线上发送。
I'm happy to use any interception mechanism such as filters, modules, etc. and the solution can be specific to IIS7. However, I'd prefer to keep it in managed code only.
我很乐意使用任何拦截机制,例如过滤器、模块等,并且解决方案可以特定于 IIS7。但是,我更愿意将它保留在托管代码中。
Any recommendations?
有什么建议吗?
Edit:I note that HttpRequesthas a SaveAsmethod which can save the request to disk but this reconstructs the request from the internal state using a load of internal helper methods that cannot be accessed publicly (quite why this doesn't allow saving to a user-provided stream I don't know). So it's starting to look like I'll have to do my best to reconstruct the request/response text from the objects... groan.
编辑:我注意到HttpRequest有一种SaveAs方法可以将请求保存到磁盘,但这会使用无法公开访问的内部帮助方法的负载从内部状态重建请求(这就是为什么这不允许保存到用户提供的流我不知道)。所以它开始看起来我必须尽我所能从对象中重建请求/响应文本......呻吟。
Edit 2:Please note that I said the wholerequest including method, path, headers etc. The current responses only look at the body streams which does not include this information.
编辑 2:请注意,我说的是整个请求,包括方法、路径、标题等。当前的响应仅查看不包含此信息的正文流。
Edit 3:Does nobody read questions around here? Five answers so far and yet not one even hints at a way to get the whole raw on-the-wire request. Yes, I know I can capture the output streams and the headers and the URL and all that stuff from the request object. I already said that in the question, see:
编辑 3:这里没有人阅读问题吗?到目前为止有五个答案,但没有一个甚至暗示一种获得整个原始在线请求的方法。是的,我知道我可以从请求对象中捕获输出流、标头和 URL 以及所有这些内容。我已经在问题中说过了,请参阅:
I can re-constitute what I believe the request looks like by inspecting all the properties of the HttpRequest object and building a string from them (and similarly for the response) but I'd really like to get hold of the actual request/response data that's sent on the wire.
我可以通过检查 HttpRequest 对象的所有属性并从它们构建一个字符串(以及类似的响应)来重新构建我认为请求的样子,但我真的很想掌握实际的请求/响应数据这是在电线上发送的。
If you know the completeraw data (including headers, url, http method, etc.) simply cannot be retrieved then that would be useful to know. Similarly if you know how to get it all in the raw format (yes, I still mean including headers, url, http method, etc.) without having to reconstruct it, which is what I asked, then that would be very useful. But telling me that I can reconstruct it from the HttpRequest/HttpResponseobjects is not useful. I know that. I already said it.
如果您知道无法检索完整的原始数据(包括标头、url、http 方法等),那么了解这些数据会很有用。同样,如果您知道如何以原始格式获取所有内容(是的,我仍然是指包括标头、url、http 方法等)而不必重建它,这就是我所问的,那么这将非常有用。但是告诉我我可以从HttpRequest/HttpResponse对象重建它是没有用的。我知道。我已经说过了。
Please note: Before anybody starts saying this is a bad idea, or will limit scalability, etc., we'll also be implementing throttling, sequential delivery, and anti-replay mechanisms in a distributed environment, so database logging is required anyway. I'm not looking for a discussion of whether this is a good idea, I'm looking for how it can be done.
请注意:在有人开始说这是一个坏主意或会限制可扩展性等之前,我们还将在分布式环境中实施节流、顺序交付和反重播机制,因此无论如何都需要数据库日志记录。我不是在寻找关于这是否是一个好主意的讨论,我正在寻找如何做到这一点。
采纳答案by Greg Beech
OK, so it looks like the answer is "no you can't get the raw data, you have to reconstruct the request/response from the properties of the parsed objects". Oh well, I've done the reconstruction thing.
好的,所以看起来答案是“不,您无法获取原始数据,您必须根据解析对象的属性重建请求/响应”。哦,我已经完成了重建工作。
回答by mckamey
Definitely use an IHttpModuleand implement the BeginRequestand EndRequestevents.
绝对使用IHttpModule和实现BeginRequest和EndRequest事件。
All of the "raw" data is present between HttpRequestand HttpResponse, it just isn't in a single raw format. Here are the parts needed to build Fiddler-style dumps (about as close to raw HTTP as it gets):
所有“原始”数据都存在于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
For the response:
对于回应:
"HTTP/1.1 " + response.Status
response.Headers // loop through these "key: value"
Note that you cannot read the response streamso you have to add a filter to the Output stream and capture a copy.
请注意,您无法读取响应流,因此您必须向输出流添加过滤器并捕获副本。
In your BeginRequest, you will need to add a response filter:
在您的 中BeginRequest,您需要添加一个响应过滤器:
HttpResponse response = HttpContext.Current.Response;
OutputFilterStream filter = new OutputFilterStream(response.Filter);
response.Filter = filter;
Store filterwhere you can get to it in the EndRequesthandler. I suggest in HttpContext.Items. There can then get the full response data in filter.ReadStream().
将其存储filter在EndRequest处理程序中可以到达的位置。我建议在HttpContext.Items. 然后可以在filter.ReadStream().
Then implement OutputFilterStreamusing the Decorator pattern as a wrapper around a stream:
然后OutputFilterStream使用装饰器模式作为流的包装器来实现:
/// <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);
}
}
回答by Sam Shiles
The following extension method on HttpRequest will create a string that can be pasted into fiddler and replayed.
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;
}
}
}
}
回答by Vincent de Lagabbe
You can use the ALL_RAW server variable to get the original HTTP headers sent with the request, then you can get the InputStream as usual:
您可以使用 ALL_RAW 服务器变量来获取与请求一起发送的原始 HTTP 标头,然后您可以像往常一样获取 InputStream:
string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];
check out: http://msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx
查看:http: //msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx
回答by John Prado
Well, I'm working on a project and did, maybe not too deep, a log using the request params:
好吧,我正在做一个项目,并且使用请求参数做了一个日志,也许不是太深:
Take a look:
看一看:
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();
}
You can decorate your controllers class for log it entirely:
您可以装饰您的控制器类以完全记录它:
[Log]
public class TermoController : Controller {...}
or log just some individual action methods
或仅记录一些单独的操作方法
[Log]
public ActionResult LoggedAction(){...}
回答by JoelBellot
Any reason you need to keep it in managed code?
有什么理由需要将它保留在托管代码中?
It is worth mentioning that you can enable Failed Trace loggingin IIS7 if you don't like re-inventing the wheel. This logs headers, the request and response body as well as many other things.
值得一提的是,如果您不喜欢重新发明轮子,您可以在 IIS7 中启用Failed Trace 日志记录。这会记录标头、请求和响应正文以及许多其他内容。


回答by GrokSrc
I went with McKAMEY's approach. Here's a module I wrote that will get you started and hopefully save you some time. You'll need to plug the Logger obviously with something that works for you:
我采用了 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
}
}
回答by FigmentEngine
use a IHttpModule:
使用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));
}
}
回答by Baburaj
if for occasional use, to get around a tight corner, how about something crude like below?
如果偶尔使用,为了绕过一个狭窄的角落,像下面这样粗暴的东西怎么样?
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
回答by Talonj
You can accomplish this in a DelegatingHandlerwithout using the OutputFiltermentioned in other answers in .NET 4.5 using the Stream.CopyToAsync()function.
您可以在DelegatingHandler不使用OutputFilter.NET 4.5 中其他答案中提到的使用该Stream.CopyToAsync()函数的情况下完成此操作。
I'm not sure on the details, but it does not trigger all of the bad things that happen when you attempt to directly read the response stream.
我不确定细节,但它不会触发您尝试直接读取响应流时发生的所有坏事。
Example:
例子:
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.
}
}

