Domanda

Sto scrivendo un servizio web (usando ASP.NET MVC) e per scopi di supporto vorremmo essere in grado di registrare le richieste e le risposte il più vicino possibile al formato raw, on-the-wire ( ovvero includendo il metodo HTTP, il percorso, tutte le intestazioni e il corpo) in un database.

Ciò di cui non sono sicuro è come ottenere questi dati nel modo meno "distorto". Posso ricostituire ciò che credo assomigli alla richiesta controllando tutte le proprietà dell'oggetto HttpRequest e costruendo una stringa da loro (e similmente per la risposta) ma mi piacerebbe davvero ottenere dei dati effettivi di richiesta / risposta inviati sul filo.

Sono felice di usare qualsiasi meccanismo di intercettazione come filtri, moduli, ecc. e la soluzione può essere specifica per IIS7. Tuttavia, preferirei tenerlo solo nel codice gestito.

Qualche consiglio?

Modifica: Prendo atto che HttpRequest ha un SaveAs che consente di salvare la richiesta su disco ma ricostruisce la richiesta dallo stato interno utilizzando un carico di metodi di supporto interni che non possono essere accessibili pubblicamente (non so perché questo non consenta il salvataggio in un flusso fornito dall'utente). Quindi inizia a sembrare che dovrò fare del mio meglio per ricostruire il testo richiesta / risposta dagli oggetti ... gemere.

Modifica 2: Nota che ho detto la richiesta intera incluso metodo, percorso, intestazioni, ecc. Le risposte attuali guardano solo i flussi corporei che non includono questo informazioni.

Modifica 3: nessuno legge domande qui? Cinque risposte finora, eppure nemmeno uno suggerisce un modo per ottenere l'intera richiesta non elaborata. Sì, so di poter acquisire i flussi di output, le intestazioni e l'URL e tutto il resto dall'oggetto richiesta. Ho già detto che nella domanda, vedi:

  

Posso ricostituire ciò che credo assomigli alla richiesta controllando tutte le proprietà dell'oggetto HttpRequest e costruendo una stringa da loro (e similmente per la risposta) ma mi piacerebbe davvero ottenere la richiesta effettiva / dati di risposta inviati sul filo.

Se si conoscono i dati completi (inclusi intestazioni, url, metodo http, ecc.) semplicemente non possono essere recuperati, sarebbe utile saperlo. Allo stesso modo se sai come ottenere tutto nel formato non elaborato (sì, intendo ancora includere intestazioni, url, metodo http, ecc.) Senza doverlo ricostruire, che è quello che ho chiesto, sarebbe molto utile. Ma dirmi che posso ricostruirlo dagli oggetti HttpRequest / HttpResponse non è utile. Lo so. L'ho già detto.


Nota: prima che qualcuno inizi a dire che questa è una cattiva idea, o limiterà la scalabilità, ecc., implementeremo anche meccanismi di limitazione, consegna sequenziale e anti-replay in un ambiente distribuito, quindi il database la registrazione è comunque richiesta. Non sto cercando una discussione se questa è una buona idea, sto cercando come può essere fatto.

È stato utile?

Soluzione 7

OK, quindi sembra che la risposta sia " no non puoi ottenere i dati grezzi, devi ricostruire la richiesta / risposta dalle proprietà degli oggetti analizzati " ;. Oh bene, ho fatto la ricostruzione.

Altri suggerimenti

Usa sicuramente un IHttpModule e implementa gli eventi BeginRequest e EndRequest .

Tutto il " raw " i dati sono presenti tra HttpRequest e HttpResponse , semplicemente non sono in un unico formato raw. Ecco le parti necessarie per costruire dump in stile Fiddler (più o meno vicino all'HTTP grezzo):

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

Per la risposta:

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

Nota che non puoi leggere il flusso di risposta , quindi devi aggiungere un filtro al flusso di output e catturare una copia.

Nel tuo BeginRequest , dovrai aggiungere un filtro di risposta:

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

Memorizza filter dove puoi trovarlo nel gestore EndRequest . Suggerisco in HttpContext.Items . È quindi possibile ottenere i dati di risposta completi in filter.ReadStream () .

Quindi implementa OutputFilterStream usando il modello Decorator come un wrapper attorno a un flusso:

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

Il seguente metodo di estensione su HttpRequest creerà una stringa che può essere incollata in fiddler e riprodotta.

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

È possibile utilizzare la variabile del server ALL_RAW per ottenere le intestazioni HTTP originali inviate con la richiesta, quindi è possibile ottenere InputStream come al solito:

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

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

Beh, sto lavorando a un progetto e ho fatto, forse non troppo in profondità, un registro usando i parametri di richiesta:

Dai un'occhiata:

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

Puoi decorare la tua classe controller per registrarla interamente:

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

o registra solo alcuni metodi di azione individuali

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

Qualche motivo necessario per tenerlo nel codice gestito?

Vale la pena ricordare che puoi abilitare Registrazione traccia non riuscita in IIS7 se non ti piace reinventare la ruota. In questo modo vengono registrate le intestazioni, il corpo della richiesta e della risposta e molte altre cose.

Registrazione traccia non riuscita

Sono andato con l'approccio di McKAMEY. Ecco un modulo che ho scritto che ti farà iniziare e spero di farti risparmiare un po 'di tempo. Ovviamente dovrai collegare il Logger con qualcosa che funzioni per te:

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

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

se per un uso occasionale, per aggirare un angolo stretto, che ne dici di qualcosa di grezzo come sotto?

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

Puoi farlo in un DelegatingHandler senza usare OutputFilter menzionato in altre risposte in .NET 4.5 usando Stream.CopyToAsync () .

Non sono sicuro dei dettagli, ma non innesca tutte le cose brutte che accadono quando si tenta di leggere direttamente il flusso di risposta.

Esempio:

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

So che non è un codice gestito, ma suggerirò un filtro ISAPI. Sono passati un paio d'anni da quando ho avuto il "piacere" di mantenere il mio ISAPI, ma da ciò che ricordo è possibile accedere a tutte queste cose, sia prima che dopo ASP.Net ha fatto la sua cosa.

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

Se un HTTPModule non è abbastanza buono per quello che ti serve, non penso che ci sia un modo gestito per farlo nella quantità richiesta di dettagli. Sarà doloroso però.

Sono d'accordo con gli altri, utilizzare un IHttpModule. Dai un'occhiata alla risposta a questa domanda, che fa quasi la stessa cosa che stai chiedendo. Registra la richiesta e la risposta, ma senza intestazioni.

Come tracciare le richieste di WebService ScriptService?

Potrebbe essere meglio farlo al di fuori della tua applicazione. Puoi impostare un proxy inverso per fare cose del genere (e molto altro). Un proxy inverso è fondamentalmente un server Web che si trova nella stanza del server e si trova tra i server Web e il client. Vedi http://en.wikipedia.org/wiki/Reverse_proxy

D'accordo con FigmentEngine, IHttpModule sembra essere la strada da percorrere.

Cerca in httpworkerrequest , readentitybody e GetPreloadedEntityBody .

Per ottenere il httpworkerrequest devi farlo:

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

dove inApp è l'oggetto httpapplication.

HttpRequest e HttpResponse pre MVC utilizzavano un GetInputStream () e GetOutputStream () che potevano essere usato a tale scopo. Non ho esaminato quelle parti in MVC, quindi non sono sicuro che siano disponibili ma potrebbe essere un'idea :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top