我正在编写一个 Web 服务(使用 ASP.NET MVC),出于支持目的,我们希望能够以尽可能接近原始的在线格式(即包括 HTTP方法、路径、所有标题和正文)到数据库中。
我不确定如何以最不“损坏”的方式获取这些数据。我可以通过检查 HttpRequest
对象的所有属性并从中构建一个字符串(对于响应也是如此)来重构我认为请求的样子,但我真的很想掌握实际的请求/通过网络发送的响应数据。
我很乐意使用任何拦截机制,例如过滤器、模块等,并且解决方案可以特定于 IIS7。但是,我更愿意将其仅保留在托管代码中。
有什么建议吗?
编辑:我注意到 HttpRequest
有一个 SaveAs
方法可以将请求保存到磁盘,但这会使用无法公开访问的大量内部辅助方法从内部状态重建请求(很明显为什么这不允许保存到我不知道的用户提供的流中)。所以看起来我必须尽我所能从对象中重建请求/响应文本......呻吟。
编辑 2:请注意,我说的是整个请求,包括方法、路径、标头等。当前响应仅查看不包含此信息的主体流。
编辑3:没有人在这里阅读问题吗?到目前为止,有五个答案,但没有一个甚至暗示了一种获取整个原始在线请求的方法。是的,我知道我可以从请求对象中捕获输出流、标头和 URL 以及所有这些内容。我已经在问题中说过,请参阅:
我可以通过检查 HttpRequest 对象的所有属性并从中构建一个字符串(对于响应也是如此)来重构我认为请求的样子,但我真的很想掌握实际的请求/响应数据这是通过网络发送的。
如果您知道 完整 原始数据(包括标头、url、http 方法等)根本无法检索,那么知道这将很有用。同样,如果您知道如何以原始格式获取所有信息(是的,我仍然是指包括标头、url、http 方法等)而无需重建它,这就是我所要求的,那么这将非常有用。但是告诉我可以从 HttpRequest
/HttpResponse
对象重建它是没有用的。我知道。我已经说过了。
请注意:在有人开始说这是一个坏主意或会限制可伸缩性等之前,我们还将在分布式环境中实现节流、顺序传递和反重放机制,因此无论如何都需要数据库日志记录。我不是在寻找关于这是否是一个好主意的讨论,我在寻找如何做到这一点。
绝对使用 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"
请注意,您无法读取响应流,因此您必须将过滤器添加到输出流并捕获副本。
在您的 BeginRequest
中,您需要添加一个响应过滤器:
HttpResponse response = HttpContext.Current.Response;
OutputFilterStream filter = new OutputFilterStream(response.Filter);
response.Filter = filter;
将 filter
存储在 EndRequest
处理程序中您可以访问的位置。我建议在 HttpContext.Items
中。然后可以在 filter.ReadStream()
中获取完整的响应数据。
然后使用装饰器模式作为流的包装器来实现 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);
}
}
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;
}
}
}
}
HttpRequestBaseExtensions
并在每个地方将 HttpRequest
更改为 HttpRequestBase
。
您可以使用 ALL_RAW 服务器变量来获取随请求发送的原始 HTTP 标头,然后您可以照常获取 InputStream:
string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];
签出:http://msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx
HttpContext.Current.Request
来获取 MVC 控制器、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();
}
您可以装饰您的控制器类以完全记录它:
[Log]
public class TermoController : Controller {...}
或仅记录一些单独的操作方法
[Log]
public ActionResult LoggedAction(){...}
有什么理由需要将其保存在托管代码中?
值得一提的是,如果您不喜欢重新发明轮子,可以在 IIS7 中启用 Failed Trace logging。这会记录标头、请求和响应正文以及许多其他内容。
https://i.stack.imgur.com/XaZ4y.png
WebApiConfig.cs
的 Register(..)
末尾添加 GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
,但这可能因版本而异。
我选择了 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
}
}
好的,所以看起来答案是“不,您无法获取原始数据,您必须从已解析对象的属性中重建请求/响应”。哦,好吧,我已经完成了重建工作。
使用 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));
}
}
如果偶尔使用,绕过一个狭窄的角落,像下面这样粗略的东西怎么样?
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
您可以使用 Stream.CopyToAsync()
函数在 DelegatingHandler
中完成此操作,而无需使用 .NET 4.5 中其他答案中提到的 OutputFilter
。
我不确定细节,但它不会触发您尝试直接读取响应流时发生的所有坏事。
例子:
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.
}
}
我知道它不是托管代码,但我会建议一个 ISAPI 过滤器。自从我拥有维护自己的 ISAPI 的“乐趣”以来已经有几年了,但据我记得,在 ASP.Net 完成它之前和之后,您都可以访问所有这些东西。
http://msdn.microsoft.com/en-us/library/ms524610.aspx
如果 HTTPModule 不足以满足您的需求,那么我只是认为没有任何托管方式可以在所需的详细信息中做到这一点。不过这样做会很痛苦。
我同意其他人的观点,使用 IHttpModule。看看这个问题的答案,它的作用与您所问的几乎相同。它记录请求和响应,但没有标头。
How to trace ScriptService WebService requests?
最好在您的应用程序之外执行此操作。你可以设置一个反向代理来做这样的事情(以及更多)。反向代理基本上是位于您的服务器机房中的 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 对象。
HttpRequest
和 HttpResponse
pre MVC 曾经有一个 GetInputStream()
和 GetOutputStream()
可用于此目的。没有研究 MVC 中的那些部分,所以我不确定它们是否可用,但可能是一个想法:)
不定期副业成功案例分享