asp.net-mvc - net - mvc ejemplos
Registro de solicitud/respuesta HTTP sin procesar en ASP.NET MVC e IIS7 (15)
¿Alguna razón por la que necesita guardarla en código administrado?
Vale la pena mencionar que puede habilitar el registro de rastreo fallido en IIS7 si no le gusta reinventar la rueda. Esto registra los encabezados, el cuerpo de solicitud y respuesta, así como muchas otras cosas.
Estoy escribiendo un servicio web (utilizando ASP.NET MVC) y para fines de soporte nos gustaría poder registrar las solicitudes y la respuesta lo más cerca posible del formato en bruto y en línea (es decir, incluyendo HTTP método, ruta, todos los encabezados y el cuerpo) en una base de datos.
De lo que no estoy seguro es de cómo obtener estos datos de la manera menos ''truncada''. Puedo volver a constituir lo que creo que parece la solicitud inspeccionando todas las propiedades del objeto HttpRequest
y construyendo una cadena a partir de ellas (y de manera similar para la respuesta) pero realmente me gustaría obtener los datos reales de solicitud / respuesta eso es enviado por el cable.
Me complace usar cualquier mecanismo de interceptación como filtros, módulos, etc. y la solución puede ser específica de IIS7. Sin embargo, preferiría mantenerlo solo en código administrado.
¿Alguna recomendación?
Editar: observo que HttpRequest
tiene un método SaveAs
que puede guardar la solicitud en el disco, pero esto reconstruye la solicitud desde el estado interno usando una carga de métodos internos de ayuda a los que no se puede acceder públicamente (por qué esto no permite guardar a un usuario -provided stream no lo sé). Así que está empezando a parecer que tendré que hacer todo lo posible para reconstruir el texto de solicitud / respuesta de los objetos ... gruñir.
Editar 2: tenga en cuenta que dije toda la solicitud, incluido el método, la ruta, los encabezados, etc. Las respuestas actuales solo miran las transmisiones corporales, que no incluyen esta información.
Edición 3: ¿Nadie lee preguntas aquí? Cinco respuestas hasta el momento y, sin embargo, ninguna incluso sugiere una forma de obtener toda la solicitud en tiempo real sin conexión. Sí, sé que puedo capturar las secuencias de salida y los encabezados y la URL y todo eso desde el objeto de solicitud. Ya dije eso en la pregunta, mira:
Puedo volver a constituir lo que creo que parece la solicitud inspeccionando todas las propiedades del objeto HttpRequest y construyendo una cadena a partir de ellas (y de manera similar para la respuesta) pero realmente me gustaría obtener los datos reales de solicitud / respuesta eso es enviado por el cable.
Si sabe que los datos brutos completos (incluidos encabezados, url, método http, etc.) simplemente no se pueden recuperar, entonces sería útil conocerlos. De manera similar, si sabes cómo obtenerlo todo en el formato sin procesar (sí, aún me refiero a incluir encabezados, url, método http, etc.) sin tener que reconstruirlo, que es lo que pregunté, sería muy útil. Pero decirme que puedo reconstruirlo desde los objetos HttpRequest
/ HttpResponse
no es útil. Yo sé eso. Ya lo dije.
Tenga en cuenta: Antes de que alguien comience a decir que esto es una mala idea, o limitará la escalabilidad, etc., también implementaremos mecanismos de aceleración, entrega secuencial y antirrepetición en un entorno distribuido, por lo que el registro de la base de datos es obligatorio. No estoy buscando una discusión sobre si esta es una buena idea, estoy buscando cómo se puede hacer.
Bueno, estoy trabajando en un proyecto y, tal vez no demasiado profundo, un registro que usa los parámetros de solicitud:
Echar un vistazo:
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();
}
Puede decorar su clase de controladores para registrarla por completo:
[Log]
public class TermoController : Controller {...}
o registra solo algunos métodos de acción individuales
[Log]
public ActionResult LoggedAction(){...}
De acuerdo con FigmentEngine, IHttpModule
parece ser el camino a seguir.
Mire en httpworkerrequest
, readentitybody
y GetPreloadedEntityBody
.
Para obtener la httpworkerrequest
, debe hacer esto:
(HttpWorkerRequest)inApp.Context.GetType().GetProperty("WorkerRequest", bindingFlags).GetValue(inApp.Context, null);
donde inApp
es el objeto httpapplication.
De acuerdo, parece que la respuesta es "no, no puedes obtener los datos sin procesar, tienes que reconstruir la solicitud / respuesta desde las propiedades de los objetos analizados". Oh, bueno, ya hice lo de la reconstrucción.
Definitivamente use un IHttpModule
e implemente los eventos BeginRequest
y EndRequest
.
Todos los datos "en bruto" están presentes entre HttpRequest
y HttpResponse
, simplemente no están en un solo formato sin formato. Aquí están las piezas necesarias para construir volcados estilo Fiddler (lo más parecido posible al HTTP sin formato):
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
Para la respuesta:
"HTTP/1.1 " + response.Status
response.Headers // loop through these "key: value"
Tenga en cuenta que no puede leer la secuencia de respuesta, por lo que debe agregar un filtro a la secuencia de salida y capturar una copia.
En BeginRequest
, deberá agregar un filtro de respuesta:
HttpResponse response = HttpContext.Current.Response;
OutputFilterStream filter = new OutputFilterStream(response.Filter);
response.Filter = filter;
Almacene el filter
donde puede obtenerlo en el controlador EndRequest
. Sugiero en HttpContext.Items
. A continuación, puede obtener los datos de respuesta completos en filter.ReadStream()
.
A continuación, implemente OutputFilterStream
utilizando el patrón Decorator como un contenedor alrededor de una secuencia:
/// <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);
}
}
El siguiente método de extensión en HttpRequest creará una cadena que se puede pegar en el violín y volver a reproducir.
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;
}
}
}
}
Es posible que sea mejor hacerlo fuera de su aplicación. Puede configurar un proxy inverso para hacer cosas como esta (y mucho más). Un proxy inverso es básicamente un servidor web que se encuentra en su sala de servidores, y se encuentra entre su (s) servidor (es) web y el cliente. Ver http://en.wikipedia.org/wiki/Reverse_proxy
Estoy de acuerdo con los demás, use un IHttpModule. Eche un vistazo a la respuesta a esta pregunta, que hace casi lo mismo que está preguntando. Registra la solicitud y la respuesta, pero sin encabezados.
Fui con el enfoque de McKAMEY. Aquí hay un módulo que escribí que lo ayudará a comenzar y espero que le permita ahorrar algo de tiempo. Necesitarás conectar el Logger obviamente con algo que funcione para ti:
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
}
}
Puede lograr esto en un DelegatingHandler
sin usar el OutputFilter
mencionado en otras respuestas en .NET 4.5 utilizando la función Stream.CopyToAsync()
.
No estoy seguro de los detalles, pero no desencadenan todas las cosas malas que ocurren cuando intentas leer directamente la secuencia de respuesta.
Ejemplo:
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.
}
}
Puede usar la variable de servidor ALL_RAW para obtener los encabezados HTTP originales enviados con la solicitud, luego puede obtener InputStream como de costumbre:
string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];
echa un vistazo a: http://msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx
Sé que no es código administrado, pero voy a sugerir un filtro ISAPI. Han pasado un par de años desde que tuve el "placer" de mantener mi propio ISAPI, pero por lo que recuerdo, pueden tener acceso a todo esto, tanto antes como después de que ASP.Net lo haya hecho.
http://msdn.microsoft.com/en-us/library/ms524610.aspx
Si un HTTPModule no es lo suficientemente bueno para lo que necesita, entonces simplemente no creo que haya ninguna manera de hacerlo de la manera adecuada con la cantidad de detalles requerida. Aunque va a ser un dolor hacerlo.
si para un uso ocasional, para rodear una esquina cerrada, ¿qué tal algo crudo como abajo?
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
use 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));
}
}
HttpRequest
y HttpResponse
pre MVC solían tener GetInputStream()
y GetOutputStream()
que podrían utilizarse con ese fin. No he visto esas partes en MVC así que no estoy seguro de que estén disponibles, pero podría ser una idea :)