سؤال

أنا أكتب خدمة ويب (باستخدام ASP.NET MVC) ولأغراض الدعم، نود أن نكون قادرين على تسجيل الطلبات والردود في أقرب وقت ممكن من التنسيق الأولي المباشر (أي بما في ذلك HTTP) الطريقة والمسار وجميع الرؤوس والنص) في قاعدة البيانات.

ما لست متأكدًا منه هو كيفية الحصول على هذه البيانات بأقل طريقة "مشوهة".يمكنني إعادة تشكيل ما أعتقد أن الطلب يبدو عليه من خلال فحص جميع خصائص ملف HttpRequest كائن وبناء سلسلة منها (وبالمثل للاستجابة) ولكني أرغب حقًا في الحصول على بيانات الطلب/الاستجابة الفعلية التي يتم إرسالها عبر السلك.

يسعدني استخدام أي آلية اعتراض مثل المرشحات والوحدات وما إلى ذلك.ويمكن أن يكون الحل محددًا لـ IIS7.ومع ذلك، أفضل الاحتفاظ بها في التعليمات البرمجية المُدارة فقط.

أي توصيات؟

يحرر: وألاحظ ذلك HttpRequest لديه SaveAs الطريقة التي يمكنها حفظ الطلب على القرص ولكن هذا يعيد بناء الطلب من الحالة الداخلية باستخدام مجموعة من الأساليب المساعدة الداخلية التي لا يمكن الوصول إليها بشكل عام (لا أعرف لماذا لا يسمح هذا بالحفظ في التدفق المقدم من المستخدم الذي لا أعرفه) .لذلك بدأ يبدو أنني سأضطر إلى بذل قصارى جهدي لإعادة بناء نص الطلب/الاستجابة من الكائنات...تأوه.

تحرير 2: يرجى ملاحظة أنني قلت جميع الطلب بما في ذلك الطريقة والمسار والرؤوس وما إلى ذلك.الإجابات الحالية تنظر فقط إلى تدفقات الجسم التي لا تتضمن هذه المعلومات.

تحرير 3: ألا يقرأ أحد الأسئلة هنا؟خمس إجابات حتى الآن، ومع ذلك لم يلمح أحد حتى إلى طريقة للحصول على الطلب الأولي عبر الإنترنت بالكامل.نعم، أعلم أنه يمكنني التقاط تدفقات الإخراج والرؤوس وعنوان URL وكل تلك الأشياء من كائن الطلب.وقد سبق أن قلت ذلك في السؤال، فانظر:

يمكنني إعادة تشكيل ما أعتقد أن الطلب يبدو عليه من خلال فحص جميع خصائص كائن HttpRequest وإنشاء سلسلة منها (وبالمثل للاستجابة) ولكني أرغب حقًا في الحصول على بيانات الطلب/الاستجابة الفعلية التي يتم إرسالها على السلك.

إذا كنت تعرف مكتمل البيانات الأولية (بما في ذلك الرؤوس، وعنوان URL، وطريقة http، وما إلى ذلك) ببساطة لا يمكن استرجاعها، لذا سيكون من المفيد معرفتها.وبالمثل، إذا كنت تعرف كيفية الحصول على كل شيء بتنسيق أولي (نعم، ما زلت أقصد تضمين الرؤوس، وعنوان url، وطريقة http، وما إلى ذلك) دون الحاجة إلى إعادة بنائها، وهو ما طلبته، فسيكون ذلك مفيدًا جدًا.لكن أخبرني أنه يمكنني إعادة بنائه من HttpRequest/HttpResponse الكائنات ليست مفيدة.وأنا أعلم ذلك.لقد قلت ذلك بالفعل.


يرجى الملاحظة:قبل أن يبدأ أي شخص بالقول أن هذه فكرة سيئة، أو ستحد من قابلية التوسع، وما إلى ذلك، سنقوم أيضًا بتنفيذ آليات التقييد والتسليم المتسلسل ومكافحة إعادة التشغيل في بيئة موزعة، لذا فإن تسجيل قاعدة البيانات مطلوب على أي حال.أنا لا أبحث عن مناقشة حول ما إذا كانت هذه فكرة جيدة، ولكني أبحث عن كيفية القيام بذلك.

هل كانت مفيدة؟

المحلول 7

حسنًا، يبدو أن الإجابة هي "لا، لا يمكنك الحصول على البيانات الأولية، عليك إعادة بناء الطلب/الاستجابة من خصائص الكائنات التي تم تحليلها".حسنًا، لقد قمت بإعادة الإعمار.

نصائح أخرى

بالتأكيد استخدم 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 إلى إنشاء سلسلة يمكن لصقها في عازف الكمان وإعادة تشغيلها.

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 Logging

لقد ذهبت مع نهج 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
    }
}

إستخدم 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

يمكنك تحقيق ذلك في 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.ألقِ نظرة على إجابة هذا السؤال، والتي تفعل تقريبًا نفس الشيء الذي تطلبه.يقوم بتسجيل الطلب والاستجابة، ولكن بدون رؤوس.

كيفية تتبع طلبات ScriptService WebService؟

قد يكون من الأفضل القيام بذلك خارج التطبيق الخاص بك.يمكنك إعداد وكيل عكسي للقيام بأشياء مثل هذه (وأكثر من ذلك بكثير).الوكيل العكسي هو في الأساس خادم ويب موجود في غرفة الخادم الخاصة بك، ويقف بين خادم (خوادم) الويب الخاص بك والعميل.يرى 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 قبل أن يكون لدى MVC ملف GetInputStream() و GetOutputStream() التي يمكن استخدامها لهذا الغرض.لم ألقي نظرة على هذا الجزء في MVC لذلك لست متأكدًا من توفرها ولكن قد تكون فكرة :)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top