捕获SOAP请求到一个ASP.NET ASMX Web服务

31
考虑记录发送至 ASP.NET ASMX Web 服务的 SOAP 请求的要求。任务是捕获发送至 Web 服务的原始 XML。
为了进行调试检查,需要记录传入消息。应用程序已经使用自己的日志库,因此理想的用法类似于以下内容:
//string or XML, it doesn't matter.
string incomingSoapRequest = GetSoapRequest();

Logger.LogMessage(incomingSoapRequest);
  • 有没有简单的解决方案来捕获传入SOAP请求的原始XML?
  • 要访问此对象和相关属性,您将处理哪些事件?
  • 是否有任何方式使IIS捕获传入请求并将其推送到日志中?
4个回答

24

您还可以通过将代码放置在Global.asax.cs中来实现。

protected void Application_BeginRequest(object sender, EventArgs e)
{
    // Create byte array to hold request bytes
    byte[] inputStream = new byte[HttpContext.Current.Request.ContentLength];

    // Read entire request inputstream
    HttpContext.Current.Request.InputStream.Read(inputStream, 0, inputStream.Length);

    //Set stream back to beginning
    HttpContext.Current.Request.InputStream.Position = 0;

    //Get  XML request
    string requestString = ASCIIEncoding.ASCII.GetString(inputStream);

}

我有一个实用方法在我的 Web 服务中,当发生我没有预料到的情况,比如未处理的异常,我会使用它来捕获请求。

    /// <summary>
    /// Captures raw XML request and writes to FailedSubmission folder.
    /// </summary>
    internal static void CaptureRequest()
    {
        const string procName = "CaptureRequest";

        try
        {
            log.WarnFormat("{0} - Writing XML request to FailedSubmission folder", procName);

            byte[] inputStream = new byte[HttpContext.Current.Request.ContentLength];

            //Get current stream position so we can set it back to that after logging
            Int64 currentStreamPosition = HttpContext.Current.Request.InputStream.Position;

            HttpContext.Current.Request.InputStream.Position = 0;

            HttpContext.Current.Request.InputStream.Read(inputStream, 0, HttpContext.Current.Request.ContentLength);

            //Set back stream position to original position
            HttpContext.Current.Request.InputStream.Position = currentStreamPosition;

            string xml = ASCIIEncoding.ASCII.GetString(inputStream);

            string fileName = Guid.NewGuid().ToString() + ".xml";

            log.WarnFormat("{0} - Request being written to filename: {1}", procName, fileName);

            File.WriteAllText(Configuration.FailedSubmissionsFolder + fileName, xml);
        }
        catch
        {
        }

    }

然后在 web.config 文件中,我存储了几个 AppSetting 值,用于定义我想要使用哪个级别来捕获请求。

    <!-- true/false - If true will write to an XML file the raw request when any Unhandled exception occurrs -->
    <add key="CaptureRequestOnUnhandledException" value="true"/>

    <!-- true/false - If true will write to an XML file the raw request when any type of error is returned to the client-->
    <add key="CaptureRequestOnAllFailures" value="false"/>

    <!-- true/false - If true will write to an XML file the raw request for every request to the web service -->
    <add key="CaptureAllRequests" value="false"/>

然后在我的 Application_BeginRequest 中,我对它进行了修改。请注意,Configuration 是我创建的静态类,用于读取 web.config 和其他区域的属性。

    protected void Application_BeginRequest(object sender, EventArgs e)
    {

        if(Configuration.CaptureAllRequests)
        {
            Utility.CaptureRequest();
        }
    }

好的。我们选择了这个。谢谢。 - David

20

捕获原始消息的一种方法是使用SoapExtensions

另一种替代方案是实现IHttpModule并在输入流到达时抓取它。

public class LogModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += this.OnBegin;
    }

    private void OnBegin(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;
        HttpContext context = app.Context;

        byte[] buffer = new byte[context.Request.InputStream.Length];
        context.Request.InputStream.Read(buffer, 0, buffer.Length);
        context.Request.InputStream.Position = 0;

        string soapMessage = Encoding.ASCII.GetString(buffer);

        // Do something with soapMessage
    }

    public void Dispose()
    {
        throw new NotImplementedException();
    }
}

这在UTF-8编码下可能不太有效,我认为。另外,你确定流是可寻址的吗?总是这样吗? - John Saunders
对于输入流,是的。这是我目前用来调试传入请求的方法,我还没有遇到任何问题。然而响应流是不可寻址的。在记录日志之前,我必须先包装响应流(原始/副本)。 - nivlam
1
输出流怎么办?我应该如何使用 IHttpModule 实现来捕获它? - gillyb
1
这给了我 soapMessage="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"。 - hakan
在Dispose中不要抛出异常!最好将其保留为空方法,什么也不做。另请参见:https://dev59.com/EHRB5IYBdhLWcg3wl4Oc#577671 - Ryan

14

你知道其实不需要创建一个 HttpModule 吗?

在你的 asmx WebMethod 中,你也可以读取 Request.InputStream 的内容。

这里是 我写的一篇关于这种方法的文章

代码如下:

using System;
using System.Collections.Generic;
using System.Web;
using System.Xml;
using System.IO;
using System.Text;
using System.Web.Services;
using System.Web.Services.Protocols;

namespace SoapRequestEcho
{
  [WebService(
  Namespace = "http://soap.request.echo.com/",
  Name = "SoapRequestEcho")]
  public class EchoWebService : WebService
  {

    [WebMethod(Description = "Echo Soap Request")]
    public XmlDocument EchoSoapRequest(int input)
    {
      // Initialize soap request XML
      XmlDocument xmlSoapRequest = new XmlDocument();

      // Get raw request body
      Stream receiveStream = HttpContext.Current.Request.InputStream;

      // Move to beginning of input stream and read
      receiveStream.Position = 0;
      using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
      {
        // Load into XML document
        xmlSoapRequest.Load(readStream);
      }

      // Return
      return xmlSoapRequest;
    }
  }
}

我尝试了Steven de Salas上面的例子,但我的HttpContext.Current始终为空。 这里有什么我做错了吗?谢谢。 - Pepito Fernandez

-2

这并不是一件容易的事情。您需要实现一个SoapExtension。前面链接中的示例展示了一个可以用于记录数据的扩展。

如果您使用的是WCF,则可以简单地设置配置以生成消息日志。


根据Steven de Salas的说法,您可以在WebMethod中使用Request.InputStream属性。我没有尝试过,但他说这是有效的。

我想测试一下http和https以及同时运行其他SoapExtensions的情况下。这些都可能会影响InputStream设置的流类型。例如,某些流无法寻找,这可能会使您的流定位在数据末尾之后,并且无法移动到开头。


2
有一个简单的方法可以做到这一点。只需在WebMethod中使用Request.InputStream即可。 - Steven de Salas
你试过这个吗?它是可寻址流吗?如果不是,那么在调用Web方法时,流已经被读取到末尾了。 - John Saunders
1
是的,它有效,您可以强制执行 Stream.Position = 0,但在 SOAP 请求期间实际上不需要这样做。 - Steven de Salas
我非常惊讶。我曾经使用过SoapExtension。在那种情况下,你得到的流是不可寻址的。与其点踩,你应该把你的评论作为答案添加进去。 - John Saunders
2
在投票之前,我发布了一篇帖子(请参见上文)。我投反对票的原因是因为我不想让人们认为这样做实际上很难。在标准SOAP请求期间调用WebMethod时,Request.InputStream可用,并且可以轻松访问。我猜在其他情况下可能无法访问或“可寻址”,但这不是问题所关注的。 - Steven de Salas
虽然有点晚了,但这个主题仍然有效。SoapExtension是正确的方法。在你的方法中读取一个请求意味着它必须是一个有效的调用并且已经正确解析。如果调用者发送了一个无效的请求,你将不会记录它,因为在反序列化过程中已经失败了。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接