web-dev-qa-db-fra.com

Consignation de la requête / réponse HTTP brute dans ASP.NET MVC & IIS7

J'écris un service Web (avec ASP.NET MVC) et, à des fins d'assistance, nous aimerions pouvoir consigner les demandes et la réponse aussi près que possible du format brut, sur le fil (c'est-à-dire, y compris HTTP). méthode, 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 mettre la main sur l'objet. données de demande/réponse réelles envoyées sur le réseau.

Je suis heureux d'utiliser n'importe quel 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?

Edit: Je note que HttpRequest a une méthode SaveAs qui permet de sauvegarder la requête. sur le disque, mais cela reconstruit la requête à partir de l'état interne en utilisant un chargement de méthodes d'assistance interne qui ne peuvent pas être consultées publiquement (ce qui explique pourquoi cela ne permet pas l'enregistrement dans un flux fourni par l'utilisateur que je ne connais pas). Il semble donc que je devrais faire de mon mieux pour reconstruire le texte de requête/réponse à partir des objets ... gémit.

Edit 2: Veuillez noter que j'ai dit la demande entière, y compris la méthode, le chemin, les en-têtes, etc. Les réponses actuelles uniquement Regardez les flux de corps qui n'incluent pas cette information.

Edit 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 et 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 les données de demande/réponse réelles. c'est envoyé sur le fil.

Si vous connaissez le complet les données brutes (y compris les en-têtes, les URL, les méthodes http, etc.) ne peuvent tout simplement pas être récupérées, il serait 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 mettre en œuvre des mécanismes de limitation, de distribution séquentielle et d'anti-relecture dans un environnement distribué. requis de toute façon. Je ne cherche pas à savoir si c'est une bonne idée, mais comment cela peut-il être fait?

136
Greg Beech

OK, il semble donc que la réponse est "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.

5
Greg Beech

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

Toutes les données "brutes" sont présentes entre HttpRequest et HttpResponse, elles ne sont tout simplement pas dans un seul format brut. Voici les éléments nécessaires à la création de dumps de style Fiddler (à peu près aussi proches que possible du HTTP brut):

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 filter où vous pourrez l’obtenir dans le gestionnaire EndRequest. Je suggère dans HttpContext.Items. Il peut alors obtenir les données de réponse complètes dans filter.ReadStream().

Puis 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);
    }
}
87
mckamey

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;
            }
        }
    }
}
47
Sam Shiles

Vous pouvez utiliser la variable serveur ALL_RAW pour obtenir les en-têtes HTTP d'origine envoyés avec la demande. Vous pouvez ensuite obtenir le flux InputStream comme d'habitude:

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

vérifier: http://msdn.Microsoft.com/en-us/library/ms524602%28VS.90%29.aspx

35
Vincent de Lagabbe

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

Regarde:

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 juste quelques méthodes d'action individuelles

[Log]
public ActionResult LoggedAction(){...}
16
John Prado

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

Il est à noter que vous pouvez activer la journalisation de trace ayant échoué 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.

Failed Trace Logging

11
JoelBellot

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 connecter le Logger à 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
    }
}
8
GrokSrc

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));
        }
    }
3
FigmentEngine

si, pour un usage occasionnel, pour éviter un petit coin, pourquoi ne pas utiliser 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
3
Baburaj

Vous pouvez y parvenir dans un DelegatingHandler sans utiliser le OutputFilter mentionné dans d'autres réponses de .NET 4.5 à l'aide de la fonction 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.
    }
}
1
Talonj

HttpRequest et HttpResponse pre MVC utilisait une GetInputStream() et GetOutputStream() pouvant être utilisée à cette fin. N'ayant pas examiné ces éléments dans MVC, je ne suis pas sûr qu'ils soient disponibles, mais cela pourrait être une idée :)

0
Rune FS

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.

0
Chris

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

0
Lance Fisher

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 requêtes de WebService ScriptService?

0
jrummell

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

Regardez dans httpworkerrequest, readentitybody et GetPreloadedEntityBody.

Pour obtenir le httpworkerrequest, vous devez faire ceci:

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

inApp est l'objet httpapplication.

0
stevenrcfox