ASP.NET MVC & IIS7의 RAW HTTP 요청/응답 로깅
-
10-07-2019 - |
문제
웹 서비스 (ASP.NET MVC 사용)를 작성하고 지원 목적으로 요청과 응답을 원시의 현장 형식 (즉, HTTP 포함)에 최대한 가깝게 로그인 할 수 있습니다. 방법, 경로, 모든 헤더 및 본문)를 데이터베이스에 넣습니다.
내가 확실하지 않은 것은이 데이터를 최소한의 '망가진'방식으로 유지하는 방법입니다. 나는 요청이 어떻게 HttpRequest
객체와 문자열을 구축하고 (그리고 응답을 위해 마찬가지로) 와이어에 전송 된 실제 요청/응답 데이터를 실제로 보유하고 싶습니다.
필터, 모듈 등과 같은 차단 메커니즘을 사용하게되어 기쁩니다. 솔루션은 IIS7에만 해당 될 수 있습니다. 그러나 나는 그것을 관리하는 코드로만 유지하는 것을 선호합니다.
권장 사항이 있습니까?
편집하다: 나는 그것을 주목한다 HttpRequest
a SaveAs
요청을 디스크에 저장할 수있는 방법이지만 이는 공개적으로 액세스 할 수없는 내부 헬퍼 메소드로드를 사용하여 내부 상태의 요청을 재구성합니다 (왜 모르는 사용자 제공 스트림에 저장을 허용하지 않는 이유) . 그래서 객체에서 요청/응답 텍스트를 재구성하기 위해 최선을 다해야하는 것처럼 보이기 시작했습니다.
편집 2 : 내가 말 했어요 전부의 방법, 경로, 헤더 등을 포함한 요청. 현재 응답은이 정보를 포함하지 않는 신체 스트림 만 살펴 봅니다.
편집 3 : 아무도 여기서 질문을 읽지 않습니까? 지금까지 5 개의 답변은 전체 원시 온 와이어 요청을 얻는 방법을 암시하지도 않습니다. 예, 출력 스트림과 헤더 및 URL 및 요청 객체의 모든 내용을 캡처 할 수 있습니다. 나는 이미 질문에서 다음을 참조했다.
HTTPRequest 객체의 모든 속성을 검사하고 문자열을 구축하여 (및 응답을 위해) 실제 요청/응답 데이터를 보유하고 싶습니다. 그것은 철사에 전송됩니다.
당신이 알고 있다면 완벽한 원시 데이터 (헤더, URL, HTTP 메소드 등)는 간단히 검색 할 수 없으면 알아볼 수 있습니다. 마찬가지로 마찬가지로 모든 것을 원시 형식으로 가져 오는 방법을 알고 있다면 (예, 여전히 헤더, URL, HTTP 메소드 등을 포함하여) 재구성하지 않고도 요청한 것입니다. 하지만 내가 그것을 재구성 할 수 있다고 말하면 HttpRequest
/HttpResponse
객체는 유용하지 않습니다. 나는 그것을 알고있다. 나는 이미 그것을 말했다.
참고 : 누군가가 나쁜 생각이라고 말하기 시작하기 전에, 확장 성 등을 제한하기 전에, 우리는 또한 분산 환경에서 스로틀, 순차적 배송 및 방지 메커니즘을 구현하므로 데이터베이스 로깅이 필요합니다. 나는 이것이 좋은 생각인지에 대한 토론을 찾고 있지 않습니다. 나는 그것이 어떻게 할 수 있는지 찾고 있습니다.
해결책 7
좋아, 따라서 대답은 "원시 데이터를 얻을 수 없으므로 구문 분석 된 개체의 속성에서 요청/응답을 재구성해야합니다"입니다. 오 글쎄, 난 재건을했다.
다른 팁
확실히 사용하십시오 IHttpModule
그리고 구현 BeginRequest
그리고 EndRequest
이벤트.
모든 "원시"데이터는 사이에 있습니다. HttpRequest
그리고 HttpResponse
, 그것은 단일 원시 형식이 아닙니다. 다음은 피들러 스타일 덤프를 만드는 데 필요한 부품입니다 (원시 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의 다음 확장 방법은 피들러에 붙여 넣고 재생할 수있는 문자열을 생성합니다.
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 서버 변수를 사용하여 원래 HTTP 헤더를 요청하여 전송 할 수 있으며 평소와 같이 입력 스트림을 얻을 수 있습니다.
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();
}
로그를 위해 컨트롤러 클래스를 장식 할 수 있습니다.
[Log]
public class TermoController : Controller {...}
또는 몇 가지 개별 조치 방법 만 기록하십시오
[Log]
public ActionResult LoggedAction(){...}
관리 코드로 유지 해야하는 이유가 있습니까?
당신이 가능하게 할 수 있다고 언급 할 가치가 있습니다 트레이스 로깅에 실패했습니다 IIS7에서 휠을 재발행하는 것을 좋아하지 않는 경우. 이것은 헤더, 요청 및 응답 본문뿐만 아니라 다른 많은 것들을 기록합니다.
나는 McKamey의 접근 방식과 함께 갔다. 여기에 내가 쓴 모듈이 있습니다. 시작하고 시간을 절약 할 수 있기를 바랍니다. 당신은 당신에게 맞는 무언가로 로거를 분명히 막아야합니다.
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
}
}
a 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
당신은 이것을 a에서 달성 할 수 있습니다 DelegatingHandler
사용하지 않고 OutputFilter
.NET 4.5를 사용하여 다른 답변에 언급되었습니다 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.
}
}
나는 그것이 관리되는 코드가 아니라는 것을 알고 있지만 ISAPI 필터를 제안 할 것입니다. 내 자신의 Isapi를 유지하는 "즐거움"이 지남에 따라 몇 년이 지났지 만, 내가 기억하는 것에서 ASP.net이 그 일을했던 전후 에이 모든 것들에 접근 할 수 있습니다.
http://msdn.microsoft.com/en-us/library/ms524610.aspx
httpmodule이 필요한 것에 충분하지 않다면 필요한 세부 사항 에서이 작업을 수행하는 관리 방법이 없다고 생각합니다. 그래도 할 수있는 고통이 될 것입니다.
나는 다른 사람들과 동의하고 ihttpmodule을 사용합니다. 이 질문에 대한 답을 살펴보십시오. 요청과 응답을 기록하지만 헤더는 없습니다.
응용 프로그램 밖에서이 작업을 수행하는 것이 가장 좋습니다. 이와 같은 일을하기 위해 리버스 프록시를 설정할 수 있습니다. 리버스 프록시는 기본적으로 서버 룸에 위치하고 웹 서버와 클라이언트 사이에있는 웹 서버입니다. 보다 http://en.wikipedia.org/wiki/reverse_proxy
피지 engine에 동의하고 IHttpModule
가는 길인 것 같습니다.
들여다보다 httpworkerrequest
, readentitybody
그리고 GetPreloadedEntityBody
.
얻기 위해 httpworkerrequest
이 작업을 수행해야합니다.
(HttpWorkerRequest)inApp.Context.GetType().GetProperty("WorkerRequest", bindingFlags).GetValue(inApp.Context, null);
어디 inApp
httpapplication 객체입니다.
HttpRequest
그리고 HttpResponse
프리 MVC는 a GetInputStream()
그리고 GetOutputStream()
그것은 그 목적으로 사용될 수 있습니다. MVC에서 그 부분을 살펴 보지 않았으므로 그들이 가용 할 수는 없지만 아이디어 일 수 있습니다 :)