Протоколирование необработанного HTTP-запроса / ответа в ASP.NET MVC и IIS7
-
10-07-2019 - |
Вопрос
Я пишу веб-сервис (используя ASP.NET MVC) и в целях поддержки мы хотели бы иметь возможность регистрировать запросы и ответы как можно ближе к необработанному, готовому формату (т.е. включая метод HTTP, путь, все заголовки и тело) в базу данных.
В чем я не уверен, так это в том, как получить эти данные наименее "искаженным" способом.Я могу воссоздать то, как, по моему мнению, выглядит запрос, проверив все свойства HttpRequest
объект и построение строки из них (и аналогично для ответа), но я бы действительно хотел получить фактические данные запроса / ответа, которые отправляются по проводам.
Я с удовольствием использую любой механизм перехвата, такой как фильтры, модули и т.д.и решение может быть специфичным для IIS7.Однако я бы предпочел сохранить его только в управляемом коде.
Есть какие-нибудь рекомендации?
Редактировать: Я отмечаю , что HttpRequest
имеет SaveAs
метод, который может сохранить запрос на диск, но при этом восстанавливает запрос из внутреннего состояния, используя множество внутренних вспомогательных методов, к которым нельзя получить доступ публично (почему это не позволяет сохранять в предоставленный пользователем поток, я не знаю).Таким образом, начинает казаться, что мне придется сделать все возможное, чтобы восстановить текст запроса / ответа из объектов...стон.
Правка 2: Пожалуйста, обратите внимание, что я сказал следующее весь запрос, включающий метод, путь, заголовки и т.д.Текущие ответы просматривают только потоки тела, которые не содержат этой информации.
Правка 3: Неужели здесь никто не читает вопросы?Пока пять ответов, и ни один даже не намекает на способ получить весь необработанный запрос по проводам.Да, я знаю, что могу захватывать выходные потоки, заголовки, URL-адрес и все такое прочее из объекта запроса.Я уже говорил об этом в вопросе, см.:
Я могу воссоздать то, как, по моему мнению, выглядит запрос, проверив все свойства объекта HttpRequest и создав из них строку (и аналогично для ответа), но я бы действительно хотел получить фактические данные запроса / ответа, которые отправляются по проводам.
Если вы знаете, что полный необработанные данные (включая заголовки, URL, http-метод и т.д.) Просто не могут быть извлечены, тогда это было бы полезно знать.Аналогично, если вы знаете, как получить все это в формате raw (да, я все еще имею в виду, включая заголовки, URL, метод http и т.д.) Без необходимости реконструировать его, о чем я и просил, тогда это было бы очень полезно.Но говорит мне, что я могу реконструировать это по HttpRequest
/HttpResponse
объекты бесполезны.Я это знаю.Я уже говорил это.
Пожалуйста, обратите внимание:Прежде чем кто-нибудь начнет говорить, что это плохая идея или ограничит масштабируемость и т.д., мы также будем внедрять механизмы регулирования, последовательной доставки и предотвращения воспроизведения в распределенной среде, поэтому ведение журнала базы данных требуется в любом случае.Я не ищу обсуждения того, хорошая ли это идея, я ищу, как это можно сделать.
Решение 7
Хорошо, похоже, ответ "нет, вы не можете получить необработанные данные, вы должны восстановить запрос / ответ по свойствам проанализированных объектов". О, хорошо, я сделал вещь восстановления.
Другие советы
Обязательно используйте IHttpModule
и реализуйте события BeginRequest
и EndRequest
.
Все " сырые " данные присутствуют между HttpRequest
и HttpResponse
, они просто не представлены в одном необработанном формате. Вот части, необходимые для создания дампов в стиле Fiddler (примерно настолько же близких к необработанному HTTP, насколько это возможно):
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
Для ответа:
"HTTP/1.1 " + response.Status
response.Headers // loop through these "key: value"
Обратите внимание, что вы не можете прочитать поток ответов , поэтому вам нужно добавить фильтр в поток вывода и захватить копию.
В BeginRequest
вам нужно будет добавить фильтр ответов:
HttpResponse response = HttpContext.Current.Response;
OutputFilterStream filter = new OutputFilterStream(response.Filter);
response.Filter = filter;
Сохраните filter
, где вы можете получить к нему доступ в обработчике EndRequest
. Я предлагаю в HttpContext.Items
. Затем можно получить полные данные ответа в filter.ReadStream ()
.
Затем реализуйте OutputFilterStream
, используя шаблон Decorator в качестве оболочки для потока:
/// <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);
}
}
Следующий метод расширения в HttpRequest создаст строку, которую можно вставить в fiddler и воспроизвести.
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;
}
}
}
}
Вы можете использовать серверную переменную ALL_RAW, чтобы получить исходные заголовки HTTP, отправленные с запросом, затем вы можете получить InputStream как обычно:
string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];
проверить: http://msdn.microsoft .com / EN-US / библиотека / ms524602% 28VS.90% 29.aspx
Ну, я работаю над проектом и, возможно, не слишком глубоко, веду журнал, используя параметры запроса:
Посмотрите:
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();
}
Вы можете украсить свой класс контроллеров для полного его логирования:
[Log]
public class TermoController : Controller {...}
или зарегистрируйте только некоторые отдельные методы действий
[Log]
public ActionResult LoggedAction(){...}
По какой причине вам нужно хранить его в управляемом коде?
Стоит упомянуть, что вы можете включить iis "rel =" noreferrer "> Не удалось зарегистрировать трассировку в IIS7, если вам не нравится заново изобретать колесо. Здесь регистрируются заголовки, тело запроса и ответа, а также многое другое.
Я пошел с подходом McKAMEY. Вот модуль, который я написал, который поможет вам начать и, надеюсь, сэкономит ваше время. Вам нужно подключить Logger, очевидно, к тому, что работает для вас:
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
}
}
используйте 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));
}
}
Если для случайного использования, чтобы обойти узкий угол, как насчет чего-то грубого, как показано ниже?
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
Вы можете сделать это с помощью DelegatingHandler
без использования OutputFilter
упоминается в других ответах в .NET 4.5 с использованием Stream.CopyToAsync()
функция.
Я не уверен в деталях, но это не запускает все плохие вещи, которые происходят, когда вы пытаетесь напрямую прочитать поток ответов.
Пример:
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.
}
}
Я знаю, что это не управляемый код, но я собираюсь предложить фильтр ISAPI. Прошло пару лет с тех пор, как я получил удовольствие. поддерживать мой собственный ISAPI, но, насколько я помню, вы можете получить доступ ко всему этому, как до, так и после того, как ASP.Net сделал это. Р>
http://msdn.microsoft.com/en-us/library /ms524610.aspx р>
Если HTTPModule не достаточно хорош для того, что вам нужно, то я просто не думаю, что есть какой-либо управляемый способ сделать это с требуемым количеством деталей. Это будет боль, хотя сделать.
Я согласен с остальными, использую IHttpModule. Посмотрите на ответ на этот вопрос, который делает почти то же самое, что вы спрашиваете. Он регистрирует запрос и ответ, но без заголовков.
Возможно, лучше сделать это за пределами вашего приложения. Вы можете настроить обратный прокси-сервер, чтобы делать такие вещи (и многое другое). Обратный прокси-сервер - это, в основном, веб-сервер, который находится в вашей серверной комнате и стоит между вашим веб-сервером (серверами) и клиентом. См. http://en.wikipedia.org/wiki/Reverse_proxy
Согласитесь с FigmentEngine, похоже, что IHttpModule
- путь.
Просмотрите httpworkerrequest
, readentitybody
и GetPreloadedEntityBody
.
Чтобы получить httpworkerrequest
, вам нужно сделать это:
(HttpWorkerRequest)inApp.Context.GetType().GetProperty("WorkerRequest", bindingFlags).GetValue(inApp.Context, null);
где inApp
- это объект httpapplication.
HttpRequest
и HttpResponse
pre MVC имели GetInputStream ()
и GetOutputStream ()
, которые могут быть используется для этой цели. Я не изучал эти части в MVC, так что я не уверен, что они доступны, но это может быть идеей:)