development

ASP.NET MVC 및 IIS7에서 원시 HTTP 요청 / 응답 로깅

big-blog 2020. 6. 23. 21:40
반응형

ASP.NET MVC 및 IIS7에서 원시 HTTP 요청 / 응답 로깅


ASP.NET MVC를 사용하여 웹 서비스를 작성 중이며 지원 목적으로 가능한 원시 원시 형식 (예 : HTTP 포함)에 최대한 가깝게 요청 및 응답을 기록 할 수 있기를 원합니다. 메소드, 경로, 모든 헤더 및 본문)을 데이터베이스에 삽입하십시오.

내가 확실하지 않은 것은이 데이터를 최소한 '혼돈 된'방법으로 유지하는 방법입니다. HttpRequest객체 의 모든 속성을 검사하고 해당 객체에서 문자열을 작성 하여 요청에 대한 응답을 재구성 할 수는 있지만 실제 요청 / 응답 데이터를 잡고 싶습니다. 와이어로 보냈습니다.

필터, 모듈 등과 같은 차단 메커니즘을 사용하게되어 기쁩니다.이 솔루션은 IIS7에만 해당 될 수 있습니다. 그러나 관리 코드로만 유지하고 싶습니다.

어떤 추천?

편집 : I 노트 HttpRequestSaveAs디스크에 요청을 저장할 수있는 방법을하지만 공개적으로 (이에 저장 허용하지 않습니다 꽤 이유에 액세스 할 수 없습니다 내부 헬퍼 메소드의 부하를 사용하여 내부 상태의 요청을 재구성 사용자가 제공하는 스트림 모르겠습니다). 따라서 객체의 요청 / 응답 텍스트를 재구성하기 위해 최선을 다해야하는 것처럼 보이기 시작했습니다 ... 신음.

편집 2 : 메소드, 경로, 헤더 등을 포함한 전체 요청을 말했음을 유의하십시오 . 현재 응답은이 정보가 포함되지 않은 본문 스트림 만보 고합니다.

편집 3 : 아무도 주변에서 질문을 읽지 않습니까? 지금까지 다섯 가지 답변이 있었지만 아직 전체 온-더-와이어 요청을 얻는 방법을 암시하는 사람은 없습니다. 예, 출력 스트림과 헤더, URL 및 요청 객체에서 URL 및 모든 내용을 캡처 할 수 있다는 것을 알고 있습니다. 나는 이미 질문에서 말했다 :

HttpRequest 객체의 모든 속성을 검사하고 그로부터 문자열을 작성하여 요청에 대한 응답을 재구성 할 수는 있지만 실제 요청 / 응답 데이터를 잡고 싶습니다. 유선으로 전송됩니다.

당신이 알고있는 경우에 전체 간단히 검색 할 수 없습니다 (등 헤더, URL, HTTP 방법 포함) 원시 데이터를 그 아는 것이 유용 할 것이다. 마찬가지로 모든 형식을 원시 형식으로 가져 오는 방법을 알고 있다면 (예, 헤더, URL, http 메소드 등을 포함하지 않고), 내가 요청한 것이므로 매우 유용합니다. 그러나 HttpRequest/ HttpResponse객체 에서 재구성 할 수 있다고 말하는 것은 유용하지 않습니다. 나도 알아 나는 이미 말했다.


참고 : 누군가 이것이 나쁜 생각이라고 말하거나 확장 성 등을 제한하기 전에 분산 환경에서 조절, 순차적 전달 및 재생 방지 메커니즘을 구현하므로 데이터베이스 로깅이 필요합니다. 나는 이것이 좋은 아이디어인지에 대한 토론을 찾고 있지 않다. 나는 그것이 어떻게 이루어질 수 있는지 찾고있다.


를 사용 IHttpModule하고 BeginRequestand 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().

그런 다음 OutputFilterStreamDecorator 패턴을 스트림 주위의 래퍼로 사용하여 구현 하십시오.

/// <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 헤더를 가져온 다음 평소와 같이 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();
    }

완전히 기록하기 위해 컨트롤러 클래스를 꾸밀 수 있습니다.

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

또는 개별 조치 방법 만 기록

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

관리 코드로 유지 해야하는 이유는 무엇입니까?

휠을 다시 발명하는 것을 원하지 않으면 IIS7에서 Failed Trace 로깅사용하도록 설정할 수 있습니다 . 이것은 헤더, 요청 및 응답 본문뿐만 아니라 다른 많은 것들을 기록합니다.

실패한 추적 로깅


나는 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));
        }
    }

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

You can accomplish this in a DelegatingHandler without using the OutputFilter mentioned in other answers in .NET 4.5 using the Stream.CopyToAsync() function.

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

I know it's not managed code, but I'm going to suggest an ISAPI filter. It's been a couple of years since I've had the "pleasure" of maintaining my own ISAPI but from what I recall you can get access to all this stuff, both before and after ASP.Net has done it's thing.

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

If a HTTPModule isn't good enough for what you need, then I just don't think there is any managed way of doing this in the required amount of detail. It's gonna be a pain to do though.


I agree with the others, use an IHttpModule. Take a look at the answer to this question, which does almost the same thing that you are asking. It logs the request and response, but without headers.

How to trace ScriptService WebService requests?


It might be best to do this outside of your application. You can set up a reverse proxy to do things like this (and much more). A reverse proxy is basically a web server that sits in your server room, and stands between your web server(s) and the client. See http://en.wikipedia.org/wiki/Reverse_proxy


Agree with FigmentEngine, IHttpModule appears to be the way to go.

에 봐 httpworkerrequest, readentitybody그리고 GetPreloadedEntityBody.

이를 수행하려면 httpworkerrequest다음을 수행하십시오.

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

inApphttpapplication 객체는 어디에 있습니까 ?


HttpRequest그리고 HttpResponseMVC가 있었었다 사전 GetInputStream()GetOutputStream()그가 그 목적을 위해 사용될 수 있습니다. MVC에서 해당 부분을 조사하지 않았으므로 사용 가능한지 확실하지 않지만 아이디어 일 수 있습니다.

참고 URL : https://stackoverflow.com/questions/1038466/logging-raw-http-request-response-in-asp-net-mvc-iis7

반응형