Logging raw HTTP request / response in ASP.NET MVC & IIS7

j'écris un service web (en utilisant ASP.NET MVC) et à des fins de support, nous aimerions pouvoir enregistrer les requêtes et les réponses aussi près que possible du format brut, sur le fil (I. e y compris 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 de savoir comment obtenir ces données de la manière la moins 'mangée'. Je peux reconstituer ce que je crois que la requête ressemble en inspectant toutes les propriétés du HttpRequest objet et construction d'une chaîne à partir d'eux (et de la même façon pour la réponse) mais je voudrais vraiment mettre la main sur les données de demande/réponse réelle qui est envoyé sur le fil.

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 garder en code géré seulement.

des recommandations?

Edit: je note que HttpRequest a une méthode SaveAs qui peut sauver la requête vers le disque, mais cela reconstruit la requête à partir de l'état interne en utilisant une charge de méthodes d'aide internes qui ne peuvent pas être accessibles publiquement (tout à fait pourquoi cela ne permet pas l'économie à un flux fourni par l'Utilisateur Je ne sais pas). Donc, il commence à sembler que je vais devoir faire de mon mieux pour reconstruire le texte de la requête/réponse à partir des objets... gémir.

Edit 2: s'il vous Plaît note que j'ai dit la demande entière incluant la méthode, le chemin, les en-têtes etc. Les réponses actuelles ne portent que sur les flux corporels qui n'incluent pas cette information.

Edit 3: personne Ne lire les questions ici? Cinq réponses jusqu'à présent et pourtant pas un seul ne laisse entrevoir un moyen d'obtenir toute la demande brute sur le fil. Oui, je sais que je peux capturer les flux de sortie et les en-têtes et L'URL et tout ce qui vient de la requête objet. J'ai déjà dit que, dans la question, voir:

je peux reconstituer ce à quoi ressemble la requête à mon avis 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 façon pour la réponse) mais j'aimerais vraiment mettre la main sur les données de requête/réponse réelles qui sont envoyées sur le fil.

si vous connaissez la complète données brutes (y compris les en-têtes, url, méthode http, etc.) ne peut tout simplement pas être récupéré alors qui serait utile de savoir. De même, si vous savez comment tout obtenir dans le format raw (Oui, je veux toujours dire y compris les en-têtes, url, méthode http, etc.) sans avoir à le reconstruire, ce que j'ai demandé, ce serait très utile. Mais me dire que je peux le reconstruire à partir des objets HttpRequest / HttpResponse n'est pas utile. Je sais qu'. Je l'ai déjà dit il.


S'il te plaît. note: avant que quelqu'un commence à dire que c'est une mauvaise idée, ou va limiter l'évolutivité, etc., nous allons également mettre en œuvre des mécanismes d'étouffement, de livraison séquentielle, et d'anti-replay dans un environnement distribué, donc l'enregistrement de base de données est nécessaire de toute façon. Je ne cherche pas à savoir si c'est une bonne idée, je cherche comment faire.

125
demandé sur Greg Beech 2009-06-24 17:44:31

15 réponses

certainement utiliser un IHttpModule et mettre en œuvre les événements BeginRequest et EndRequest .

toutes les données" brutes "sont présentes entre HttpRequest et HttpResponse , ce n'est tout simplement pas dans un seul format brut. Voici les parties nécessaires pour construire des dumps de style Fiddler (aussi proche 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 donc, vous devez ajouter un filtre pour le flux de Sortie et 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;

Magasin filter , où vous pouvez l'obtenir dans le EndRequest gestionnaire. Je suggère dans HttpContext.Items . Il peut alors obtenir les données complètes de réponse dans filter.ReadStream() .

puis mettre en œuvre OutputFilterStream en utilisant le motif décorateur comme un enroulement autour d'un ruisseau:

/// <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);
    }
}
84
répondu mckamey 2013-08-04 22:29:35

la méthode d'extension suivante sur HttpRequest créera une chaîne qui peut ê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;
            }
        }
    }
}
45
répondu Sam Shiles 2013-08-23 11:38:13

vous pouvez utiliser la variable ALL_RAW server pour obtenir les en-têtes HTTP originaux envoyés avec la requête, puis vous pouvez obtenir la saisie comme d'habitude:

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

check out: http://msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx

35
répondu Vincent de Lagabbe 2010-04-26 17:38:39

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

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 le log it entièrement:

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

ou le journal à seulement quelques méthodes d'action

[Log]
public ActionResult LoggedAction(){...}
16
répondu John Prado 2009-11-13 05:12:25

y a-t-il une raison pour que vous deviez le garder en code géré?

il est important de mentionner que vous pouvez activer Failed Trace logging dans IIS7 si vous n'aimez pas réinventer la roue. Ceci enregistre les en-têtes, le corps de la requête et de la réponse ainsi que beaucoup d'autres choses.

Failed Trace Logging

9
répondu JoelBellot 2014-03-12 09:05:17

J'ai suivi L'approche de McKAMEY. Voici un module que j'ai écrit qui vous permettra de commencer et j'espère vous faire économiser du temps. Vous aurez besoin de brancher l'enregistreur évidemment 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
    }
}
8
répondu GrokSrc 2012-06-07 16:14:24

OK, donc on dirait que la réponse est "non vous ne pouvez pas obtenir les données brutes, vous devez reconstruire la requête/réponse à partir des propriétés des objets analysés". Eh bien, j'ai fait de la reconstruction chose.

5
répondu Greg Beech 2009-11-26 13:48:54

utiliser 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));
        }
    }
4
répondu FigmentEngine 2009-06-25 21:14:52

si pour une utilisation occasionnelle, pour se déplacer dans un coin étroit, que diriez-vous de quelque chose de grossier 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
répondu Baburaj 2013-09-23 05:41:31

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 requête et la réponse, mais sans en-tête.

comment retracer les demandes de service Web de ScriptService?

0
répondu jrummell 2017-05-23 11:46:57

il pourrait être préférable de faire cela en dehors de votre application. Vous pouvez configurer un mandataire inverse pour faire des choses comme ceci (et bien plus encore). Un mandataire inversé est essentiellement un serveur web qui se trouve dans votre salle de serveur, et se trouve entre votre serveur web(s) et le client. Voir http://en.wikipedia.org/wiki/Reverse_proxy

0
répondu Lance Fisher 2010-06-13 05:16:19

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

regardez 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
répondu stevenrcfox 2014-01-28 22:20:08

vous pouvez accomplir ceci dans un DelegatingHandler sans utiliser le OutputFilter mentionné dans D'autres réponses dans .NET 4.5 en utilisant la fonction Stream.CopyToAsync() .

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

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.
    }
}
0
répondu Talonj 2015-02-18 23:48:06

HttpRequest et HttpResponse pré MVC utilisé pour avoir un GetInputStream() et GetOutputStream() qui pourrait être utilisé à cette fin. N'ont pas regardé dans cette partie dans MVC donc je ne suis pas sûr qu'ils sont disponibles, mais pourrait être une idée:)

0
répondu Rune FS 2017-12-15 21:08:53

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" d'entretenir mon propre ISAPI mais d'après ce que je me rappelle, vous pouvez avoir accès à tout cela, avant et après ASP.Net a fait son truc.

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 il existe tout moyen géré de le faire avec le niveau de détail requis. Ça va être une douleur à faire.

-1
répondu Chris 2009-07-02 12:13:27