ASP.NET MVCでの生のHTTPリクエスト/レスポンスのロギング& IIS7
-
10-07-2019 - |
質問
(ASP.NET MVCを使用して)Webサービスを作成していますが、サポートの目的で、可能な限り生のオンザワイヤ形式に近い要求と応答を記録できるようにしたいです(つまり、HTTPメソッド、パス、すべてのヘッダー、および本文をデータベースに含めます。
私が確信していないのは、このデータを最小限の「破損」した方法で取得する方法です。 HttpRequest
オブジェクトのすべてのプロパティを検査し、それらから文字列を作成することで(および同様に応答用に)、リクエストがどのように見えるかを再構成できますが、本当に取得したいです有線で送信される実際の要求/応答データの。
フィルタ、モジュールなどのインターセプトメカニズムを使用できることをうれしく思います。ソリューションはIIS7に固有のものにすることができます。ただし、マネージコードでのみ保持することをお勧めします。
何か推奨事項はありますか?
編集: HttpRequest
には SaveAs
メソッド。リクエストをディスクに保存できますが、これは、内部ヘルパーメソッドのロードを使用して内部状態からリクエストを再構築します。パブリックにアクセスされる(これで、ユーザーが指定したストリームへの保存が許可されない理由はわかりません)だから、オブジェクトからの要求/応答テキストを再構築するために最善を尽くさなければならないように見え始めています...うめき声。
編集2:メソッド、パス、ヘッダーなどを含む whole リクエストを言ったことに注意してください。現在のレスポンスは、これを含まないボディストリームのみを表示します情報。
編集3:この辺りで質問を読む人はいませんか?これまでに5つの回答がありましたが、未加工のオンザワイヤ要求全体を取得する方法を示唆するものすらありませんでした。はい、出力ストリーム、ヘッダー、URL、および要求オブジェクトからすべてのものをキャプチャできることを知っています。私はすでに質問でそれを言った:参照:
HttpRequestオブジェクトのすべてのプロパティを検査し、それらから文字列を作成することで(および同様に応答用に)、リクエストがどのようなものであると思うかを再構成できますが、実際のリクエストを取得したいです有線で送信される/ responseデータ。
完全な生データ(ヘッダー、URL、httpメソッドなどを含む)を取得できない場合、それは知っておくと便利です。同様に、すべてを未加工形式で取得する方法を知っている場合(はい、まだヘッダー、URL、httpメソッドなどを含めることを意味します)、再構築する必要はありません。しかし、 HttpRequest
/ HttpResponse
オブジェクトから再構築できると言っても役に立ちません。そんなこと知ってる。すでに言った。
注意:誰かがこれが悪い考えだとか、スケーラビリティを制限するなどと言い始める前に、分散環境でスロットル、シーケンシャル配信、アンチリプレイメカニズムも実装するので、データベースとにかくロギングが必要です。これが良いアイデアであるかどうかの議論を探しているのではなく、どうやってそれを行うことができるのか探しています。
解決 7
OK、答えは「いいえ、生データを取得できません。解析されたオブジェクトのプロパティから要求/応答を再構築する必要があります」のように見えます。まあ、私は再建の事をやった。
他のヒント
IHttpModule
を確実に使用し、 BeginRequest
および EndRequest
イベントを実装します。
すべての" raw"データは 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;
EndRequest
ハンドラーで取得できる filter
を保存します。 HttpContext.Items
で提案します。その後、 filter.ReadStream()
で完全な応答データを取得できます。
次に、デコレータパターンをストリームのラッパーとして使用して、 OutputFilterStream
を実装します。
/// <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の次の拡張メソッドは、フィドラーに貼り付けて再生できる文字列を作成します。
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 / library / 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(){...}
マネージコードに保持する必要がある理由は何ですか?
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
これは、 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を使用します。この質問への答えを見てください。あなたが尋ねているのとほぼ同じことをします。要求と応答を記録しますが、ヘッダーはありません。
アプリケーションの外部でこれを行うのが最善かもしれません。リバースプロキシを設定して、このような(およびその他の)ことを行うことができます。リバースプロキシは、基本的にサーバールームに配置され、Webサーバーとクライアントの間にあるWebサーバーです。 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
以前のMVCでは、 GetInputStream()
および GetOutputStream()
を使用できました。その目的に使用されます。 MVCでこれらの部分を調べていないので、それらが利用できるかどうかはわかりませんが、アイデアかもしれません:)