Question

J'écris un service Web (avec ASP.NET MVC). Pour des raisons d'assistance, nous aimerions pouvoir enregistrer les demandes et les réponses aussi près que possible du format brut, sur le fil ( c'est-à-dire incluant la méthode HTTP, le chemin, tous les en-têtes et le corps) dans une base de données.

Ce dont je ne suis pas sûr, c'est comment obtenir ces données de la manière la moins malmenée. Je peux reconstituer l'apparence de la demande en inspectant toutes les propriétés de l'objet HttpRequest et en construisant une chaîne à partir de celles-ci (et de la même manière pour la réponse), mais j'aimerais vraiment rester en retrait. des données de demande / réponse réelles envoyées sur le réseau.

Je suis heureux d'utiliser tout mécanisme d'interception tel que des filtres, des modules, etc., et la solution peut être spécifique à IIS7. Cependant, je préférerais le conserver uniquement dans le code géré.

Des recommandations?

Modifier: Je remarque que HttpRequest a un SaveAs méthode qui peut enregistrer la requête sur le disque, mais reconstruit la requête à partir de l'état interne à l'aide d'un chargement de méthodes d'assistance interne ne pouvant pas être accessible publiquement (c'est pourquoi je ne sais pas pourquoi cela ne permet pas de sauvegarder dans un flux fourni par l'utilisateur). Il semble donc que je devrais faire de mon mieux pour reconstruire le texte de requête / réponse à partir des objets ... gémir.

Éditer 2: Veuillez noter que j'ai dit la requête entière , y compris la méthode, le chemin, les en-têtes, etc. Les réponses actuelles ne concernent que les flux de corps qui ne l'incluent pas. informations.

Modifier 3: Personne ne lit les questions ici? Cinq réponses à ce jour et pourtant aucune ne laisse supposer un moyen d'obtenir la totalité de la demande brute en fil. Oui, je sais que je peux capturer les flux de sortie, les en-têtes, l'URL et tout le reste à partir de l'objet de requête. J'ai déjà dit que dans la question, voir:

  

Je peux reconstituer l'apparence de la demande en inspectant toutes les propriétés de l'objet HttpRequest et en construisant une chaîne à partir de celles-ci (et de la même manière pour la réponse), mais j'aimerais vraiment obtenir la demande. / données de réponse envoyées sur le réseau.

Si vous connaissez les données brutes complètes (y compris les en-têtes, les URL, les méthodes http, etc.), vous ne pouvez tout simplement pas les récupérer, il serait donc utile de les connaître. De même, si vous savez comment obtenir tout cela dans le format brut (oui, je veux dire, y compris les en-têtes, les URL, la méthode http, etc.) sans avoir à le reconstruire, ce que j'ai demandé, alors ce serait très utile. Mais me dire que je peux le reconstruire à partir des objets HttpRequest / HttpResponse n’est pas utile. Je le sais. Je l'ai déjà dit.

Remarque: avant que quiconque ne commence à dire que c'est une mauvaise idée, ou limitera l'évolutivité, etc., nous allons également implémenter des mécanismes de limitation, de distribution séquentielle et d'anti-relecture dans un environnement distribué. l'enregistrement est nécessaire de toute façon. Je ne cherche pas à savoir si c'est une bonne idée, mais à chercher comment faire.

Était-ce utile?

La solution 7

OK, il semble que la réponse soit "non, vous ne pouvez pas obtenir les données brutes, vous devez reconstruire la demande / réponse à partir des propriétés des objets analysés". Oh bien, j'ai fait la reconstruction.

Autres conseils

Utilisez certainement un IHttpModule et implémentez les événements BeginRequest et EndRequest .

Tous les " raw " des données sont présentes entre HttpRequest et HttpResponse , il ne s'agit tout simplement pas d'un format brut. Voici les éléments nécessaires à la création de dumps de style Fiddler (à peu près aussi proches du HTTP brut que possible):

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

Pour la réponse:

"HTTP/1.1 " + response.Status
response.Headers // loop through these "key: value"

Notez que vous ne pouvez pas lire le flux de réponse , vous devez donc ajouter un filtre au flux de sortie et en capturer une copie.

Dans votre BeginRequest , vous devrez ajouter un filtre de réponse:

HttpResponse response = HttpContext.Current.Response;
OutputFilterStream filter = new OutputFilterStream(response.Filter);
response.Filter = filter;

Stockez filtre où vous pouvez vous y rendre dans le gestionnaire EndRequest . Je suggère dans HttpContext.Items . Vous pouvez alors obtenir les données de réponse complètes dans filter.ReadStream () .

Ensuite, implémentez OutputFilterStream en utilisant le motif Decorator pour envelopper un flux:

/// <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);
    }
}

La méthode d'extension suivante sur HttpRequest créera une chaîne pouvant être collée dans fiddler et rejouée.

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

Vous pouvez utiliser la variable serveur ALL_RAW pour obtenir les en-têtes HTTP d'origine envoyés avec la demande, puis vous pouvez obtenir le flux d'entrée comme d'habitude:

string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];

consultez: http://msdn.microsoft .com / fr-us / library / ms524602% 28VS.90% 29.aspx

Eh bien, je travaille sur un projet et ai fait, peut-être pas trop, un journal utilisant les paramètres de requête:

Regardez:

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();
    }

Vous pouvez décorer votre classe de contrôleurs pour la journaliser entièrement:

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

ou connectez-vous uniquement à des méthodes d'action individuelles

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

Avez-vous besoin de le conserver en code managé?

Il est à noter que vous pouvez activer Échec de l'enregistrement de la trace dans IIS7 si vous n'aimez pas réinventer la roue. Cela enregistre les en-têtes, le corps de la demande et de la réponse, ainsi que de nombreux autres éléments.

Échec de la journalisation du suivi

Je suis allé avec l'approche de McKAMEY. Voici un module que j'ai écrit qui vous permettra de démarrer et, espérons-le, de vous faire gagner du temps. Vous devrez évidemment brancher l'enregistreur avec quelque chose qui fonctionne pour vous:

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

utilisez un 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));
        }
    }

si, pour un usage occasionnel, pour contourner un coin étroit, qu’en est-il de quelque chose de brut comme ci-dessous?

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

Vous pouvez accomplir cela dans un DelegatingHandler sans utiliser le OutputFilter mentionné dans d'autres réponses dans .NET 4.5 en utilisant le Stream.CopyToAsync () .

Je ne suis pas sûr des détails, mais cela ne déclenche pas toutes les mauvaises choses qui se produisent lorsque vous essayez de lire directement le flux de réponses.

Exemple:

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

Je sais que ce n'est pas du code géré, mais je vais suggérer un filtre ISAPI. Cela fait quelques années que je n'ai pas eu le "plaisir" de maintenir mon propre ISAPI, mais d'après ce que je me souviens, vous pouvez avoir accès à tout cela, à la fois avant et après qu'ASP.Net l'ait fait.

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

Si un HTTPModule n'est pas assez bon pour ce dont vous avez besoin, alors je ne pense pas qu'il existe un moyen géré de le faire avec la quantité de détails requise. Cela va être pénible à faire cependant.

Je suis d’accord avec les autres, utilisez un IHttpModule. Regardez la réponse à cette question, qui fait presque la même chose que vous demandez. Il enregistre la demande et la réponse, mais sans en-têtes.

Comment suivre les demandes ScriptService WebService?

Il serait peut-être préférable de le faire en dehors de votre application. Vous pouvez configurer un proxy inverse pour faire des choses comme celle-ci (et bien plus encore). Un proxy inverse est essentiellement un serveur Web situé dans votre salle des serveurs et placé entre votre ou vos serveurs Web et le client. Voir http://en.wikipedia.org/wiki/Reverse_proxy

En accord avec FigmentEngine, IHttpModule semble être la voie à suivre.

Recherchez httpworkerrequest , readentitybody et GetPreloadedEntityBody .

Pour obtenir la httpworkerrequest , procédez comme suit:

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

inApp est l'objet httpapplication.

HttpRequest et HttpResponse pre MVC utilisait un GetInputStream () et un GetOutputStream () pouvant être utilisé à cette fin. Nous n’avons pas étudié ces éléments dans MVC, je ne suis donc pas sûr qu’ils soient disponibles, mais cela pourrait être une idée:)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top