2009-06-24 4 views
121

Я пишу веб-службу (используя ASP.NET MVC), и в целях поддержки мы хотели бы иметь возможность регистрировать запросы и ответы в качестве близких насколько это возможно, к необработанному, в-проводном формате (т. е. включая HTTP-метод, путь, все заголовки и тело) в базу данных.Регистрация необработанного HTTP-запроса/ответа в ASP.NET MVC & IIS7

Что я не уверен в том, как получить эти данные в наименее «искаженном» виде. Я могу переформулировать, на мой взгляд, запрос, просматривая все свойства объекта HttpRequest и строя из них строку (и аналогично для ответа), но я действительно хотел бы получить фактические данные запроса/ответа это отправлено по проводам.

Я рад использовать любой механизм перехвата, такой как фильтры, модули и т. Д., И это решение может быть специфичным для IIS7. Однако я бы предпочел сохранить его только в управляемом коде.

Любые рекомендации?

Edit: Хочу отметить, что HttpRequest имеет SaveAs метод, который может сохранить запрос на диске, но это реконструирует запрос от внутреннего состояния с использованием нагрузки внутренних вспомогательных методов, которые не могут быть доступны публично (вполне, почему это Безразлично» t позволяет сохранять поток, предоставленный пользователем, я не знаю). Таким образом, это начинает выглядеть так, как будто я должен сделать все возможное, чтобы восстановить текст запроса/ответа от объектов ... стон.

Edit 2: Пожалуйста, обратите внимание, что я сказал запрос весь включая метод, путь, заголовки и т.д. текущие ответы только смотреть на потоки тела, которые не включают в себя эту информацию.

Редактировать 3: Не читает ли здесь какие-либо вопросы? Пять ответов до сих пор, и все же никто даже не намекает на способ получить весь необработанный запрос на провод. Да, я знаю, что могу захватить выходные потоки, заголовки и URL-адрес, и все это из объекта запроса. Я уже говорил, что в этом вопросе, см:

я могу повторно представлять то, что я считаю, что запрос выглядит, проверяя все свойства HttpRequest объекта и построение строки из них (и аналогично для ответа) но я действительно хотел бы получить фактические данные запроса/ответа, которые были отправлены на проводе.

Если вы знаете полных необработанных данных (включая заголовки, URL, метод HTTP и т.д.) просто не может быть восстановлен, то это было бы полезно знать. Точно так же, если вы знаете, как получить все это в необработанном формате (да, я все еще имею в виду, включая заголовки, url, http-метод и т. Д.) Без необходимости его реконструировать, что я и задал, тогда это было бы очень полезно. Но, рассказывая мне, что я могу восстановить его с объектов HttpRequest/HttpResponse, это не полезно. Я знаю это. Я уже это сказал.


Пожалуйста, обратите внимание: Прежде чем кто-нибудь начинает говорить, что это плохая идея, или ограничить масштабируемость и т.д., мы будем также реализации удушения, последовательная доставки и механизмы против повторного воспроизведения в распределенной среде, поэтому в любом случае требуется ведение базы данных. Я не ищу обсуждения, является ли это хорошей идеей, я ищу, как это можно сделать.

+0

@Kev - Нет, это RESTful сервис, реализованный с помощью ASP.NET MVC –

+0

Возможно, это можно сделать с помощью IIS7 и собственного модуля - http://msdn.microsoft.com/ en-us/library/ms694280.aspx –

+0

Вам удалось реализовать это? Просто любопытно, вы применили какую-либо стратегию буфера для записи в db? – systempuntoout

ответ

5

ОК, так что это выглядит как ответ «нет, вы не можете получить исходные данные, вы должны реконструировать запрос/ответ от свойств проанализированных объектов ». Хорошо, я сделал операцию реконструкции.

+3

Вы видели комментарий Vineus о ServerVariables ["ALL_RAW"]? Я еще не пробовал это сам, но документировано, чтобы вернуть исходную информацию заголовка точно как отправлено клиентом. Даже если документ окажется неправильным, и он делает реконструкцию, эй, бесплатную реконструкцию :-) –

0

HttpRequest и HttpResponse предварительно MVC используется, чтобы иметь GetInputStream() и GetOutputStream(), которые могут быть использованы для этой цели. Не смотрите в те части в MVC, так я не уверен, что они availavle, но может быть идея :)

4

использовать 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)); 
     } 
    } 
+2

Per Alex и мой собственный опыт, я не думаю, что вы можете читать HttpResponse.OutputStream, поэтому ваш метод ведения журнала в методе ProcessResponse, вероятно, не сработает. –

+0

Уильям прав. HttpResponse.OutputStream не читается. Я нахожу решение, которое должно использовать HttpResponse.Filter и заменять выходной поток по умолчанию своим. –

+0

http://www.endurasoft.com/blog/post/implementing-an-asynchronous-httpmodule.aspx async Httpmodule лучше? –

0

Согласен с FigmentEngine, IHttpModule, как представляется, путь ,

Ознакомиться с httpworkerrequest, readentitybody и GetPreloadedEntityBody.

Чтобы получить httpworkerrequest вам нужно сделать, это:

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

где inApp является объектом HttpApplication.

+1

Я уже говорил, что ответ не подходит, потому что он не фиксирует большую часть информации, которую я просил. Как этот ответ может быть полезен? –

+0

Подробнее объясните, как этот ответ может быть полезен? –

-1

Я знаю, что это не управляемый код, но я собираюсь предложить фильтр ISAPI. Прошло пару лет с тех пор, как у меня было «удовольствие» от поддержки моего ISAPI, но из того, что я помню, вы можете получить доступ ко всему этому, как до, так и после того, как ASP.Net это сделала.

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

Если HTTPModule не достаточно хорош для того, что вам нужно, то я просто не думаю, что есть какие-либо удалось способ сделать это в требуемом количестве деталей. Однако это будет боль.

0

Я согласен с другими, используйте IHttpModule. Взгляните на ответ на этот вопрос, который делает почти то же самое, что вы просите. Он регистрирует запрос и ответ, но без заголовков.

How to trace ScriptService WebService requests?

13

Ну, я работаю над проектом, и сделал, может быть, не слишком глубоко, журнал, используя запрос Params:

Посмотрите:

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 jus т некоторые индивидуальные методы действия

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

Определенно использовать IHttpModule и реализовать BeginRequest и EndRequest события.

Все «сырые» данные присутствуют между HttpRequest и HttpResponse, это просто не в одном необработанном формате.Вот детали, необходимые для создания отвалов Скрипач стиле (о как можно ближе к сырому 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); 
    } 
} 
+1

Хороший ответ. Один комментарий: вы сказали: «Тогда можно получить полные данные ответа в filter.ToString()». -Вы не имеете в виду фильтр.ReadStream()? (Я реализую в vb.net не C#, но если я запускаю ToString, я просто получаю имя класса в виде строки .ReadStream возвращает желаемый орган ответа. – Adam

+0

Я согласен, хороший ответ. Я использовал его в качестве основы для пользовательский журнал, но теперь столкнулся с проблемой, когда некоторые заголовки отсутствуют, а самое главное при использовании сжатия IIS. Я не могу получить доступ к окончательному сжатому отклику. Я начал новый связанный вопрос (http://stackoverflow.com/questions/11084459/logging-raw-and-compression-http-response-in-asp-net-iis7) – Chris

+2

Я думаю, что mckamey - гений. Можете ли вы пойти на работу в Microsoft, чтобы мы просто получили интеллектуальные решения вместо того, чтобы иметь блестящие обходные пути? – Abacus

34

Вы можете использовать переменный сервер ALL_RAW, чтобы получить исходный HTTP заголовки, посланные с запросом, то вы можете получить InputStream, как обычно:

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

чек: http://msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx

+0

Это сработало и для меня. Даже не нужно было быть в обработчике. Я смог получить к нему доступ со страницы. – Helephant

+3

Или в контексте сервера ASP.NET используйте: this.Request.ServerVariables ["ALL_RAW"]; –

+0

Я не могу получить тело запроса из Request.InputStream, он возвращает «» для меня каждый раз, однако ALL_RAW отлично работает для возврата заголовков запросов, поэтому этот ответ наполовину прав. – Justin

0

Это могло бы быть лучше сделать это за пределами вашей заявки. Вы можете настроить обратный прокси-сервер, чтобы делать такие вещи (и многое другое). Обратный прокси-сервер - это в основном веб-сервер, который находится в вашей серверной комнате и находится между вашим веб-сервером и клиентом. См. http://en.wikipedia.org/wiki/Reverse_proxy

44

Следующий способ расширения на HttpRequest создаст строку, которая может быть вставлена ​​в скрипач и воспроизведена.

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

Очень хороший код! Но для этого для работы с MVC 4 мне пришлось изменить имя класса на «HttpRequestBaseExtensions» и изменить «HttpRequest» на «HttpRequestBase» на каждом месте. – Dmitry

7

Я пошел с подходом Маккамай. Вот модуль, который я написал, который поможет вам начать и, надеюсь, сэкономит вам время. Вам необходимо подключить 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 
    } 
} 
+2

Вы не можете безопасно использовать приложение app.Response.Filter для чего угодно, кроме Stream. Другие HttpModules могут обернуть ваш фильтр ответов собственными, и в этом случае вы получите недопустимое исключение литых. –

+0

Должно ли быть «Encoding.UTF8» или, может быть, «Encoding.Default» при чтении потока запросов? Или просто используйте «StreamReader» ([с утилизационными оговорками] (http://stackoverflow.com/questions/21971467/why-cant-i-read-http-request-input-stream-twice)) – drzaus

3

если для эпизодического использования, чтобы обойти тугой угол, как о чем-то сырой, как показано ниже?

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 
9

По какой-либо причине вам необходимо сохранить его в управляемом коде?

Следует отметить, что вы можете включить Failed Trace logging в IIS7, если вам не нравится изобретать колесо. Это заголовки журналов, тело запроса и ответа, а также многое другое.

Failed Trace Logging

+0

Что делать, если это нет «Неудача? – Sinaesthetic

+5

Вы также можете использовать журнал Failed Trace с протоколом HTTP 200 OK, так что ошибки не могут быть зарегистрированы. – JoelBellot

+1

Это, безусловно, самое простое решение. – Kehlan

0

Вы можете сделать это в 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. 
    } 
} 
Смежные вопросы