Получение данных RAW-мыла из веб-эталонного клиента, работающего на ASP.net - PullRequest
90 голосов
/ 19 ноября 2008

Я пытаюсь пристрелить клиента веб-службы в моем текущем проекте. Я не уверен в платформе Service Server (скорее всего, LAMP). Я считаю, что на их стороне забора есть ошибка, поскольку я устранил потенциальные проблемы с моим клиентом. Клиент - это стандартный веб-прокси-сервер типа ASMX, автоматически генерируемый из службы WSDL.

Мне нужно получить сообщения RAW SOAP (запросы и ответы)

Как лучше всего это сделать?

Ответы [ 8 ]

127 голосов
/ 09 января 2009

Я внес следующие изменения в web.config, чтобы получить конверт SOAP (запрос / ответ). Это выведет всю необработанную информацию SOAP в файл trace.log.

<system.diagnostics>
  <trace autoflush="true"/>
  <sources>
    <source name="System.Net" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
    <source name="System.Net.Sockets" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener"
      initializeData="trace.log"/>
  </sharedListeners>
  <switches>
    <add name="System.Net" value="Verbose"/>
    <add name="System.Net.Sockets" value="Verbose"/>
  </switches>
</system.diagnostics>
34 голосов
/ 19 ноября 2008

Вы можете реализовать SoapExtension, которое регистрирует полный запрос и ответ в файл журнала. Затем вы можете включить SoapExtension в web.config, что позволяет легко включать / выключать его в целях отладки. Вот пример, который я нашел и изменил для собственного использования, в моем случае регистрация была произведена log4net, но вы можете заменить методы журнала своими.

public class SoapLoggerExtension : SoapExtension
{
    private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
    private Stream oldStream;
    private Stream newStream;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }

    public override object GetInitializer(Type serviceType)
    {
        return null;
    }

    public override void Initialize(object initializer)
    {

    }

    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        oldStream = stream;
        newStream = new MemoryStream();
        return newStream;
    }

    public override void ProcessMessage(SoapMessage message)
    {

        switch (message.Stage)
        {
            case SoapMessageStage.BeforeSerialize:
                break;
            case SoapMessageStage.AfterSerialize:
                Log(message, "AfterSerialize");
                    CopyStream(newStream, oldStream);
                    newStream.Position = 0;
                break;
                case SoapMessageStage.BeforeDeserialize:
                    CopyStream(oldStream, newStream);
                    Log(message, "BeforeDeserialize");
                break;
            case SoapMessageStage.AfterDeserialize:
                break;
        }
    }

    public void Log(SoapMessage message, string stage)
    {

        newStream.Position = 0;
        string contents = (message is SoapServerMessage) ? "SoapRequest " : "SoapResponse ";
        contents += stage + ";";

        StreamReader reader = new StreamReader(newStream);

        contents += reader.ReadToEnd();

        newStream.Position = 0;

        log.Debug(contents);
    }

    void ReturnStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    void ReceiveStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    public void ReverseIncomingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseOutgoingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseStream(Stream stream)
    {
        TextReader tr = new StreamReader(stream);
        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);

        TextWriter tw = new StreamWriter(stream);
        stream.Position = 0;
        tw.Write(strReversed);
        tw.Flush();
    }
    void CopyAndReverse(Stream from, Stream to)
    {
        TextReader tr = new StreamReader(from);
        TextWriter tw = new StreamWriter(to);

        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);
        tw.Write(strReversed);
        tw.Flush();
    }

    private void CopyStream(Stream fromStream, Stream toStream)
    {
        try
        {
            StreamReader sr = new StreamReader(fromStream);
            StreamWriter sw = new StreamWriter(toStream);
            sw.WriteLine(sr.ReadToEnd());
            sw.Flush();
        }
        catch (Exception ex)
        {
            string message = String.Format("CopyStream failed because: {0}", ex.Message);
            log.Error(message, ex);
        }
    }
}

[AttributeUsage(AttributeTargets.Method)]
public class SoapLoggerExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1; 

    public override int Priority
    {
        get { return priority; }
        set { priority = value; }
    }

    public override System.Type ExtensionType
    {
        get { return typeof (SoapLoggerExtension); }
    }
}

Затем вы добавляете следующий раздел в ваш web.config, где YourNamespace и YourAssembly указывают на класс и сборку вашего SoapExtension:

<webServices>
  <soapExtensionTypes>
    <add type="YourNamespace.SoapLoggerExtension, YourAssembly" 
       priority="1" group="0" />
  </soapExtensionTypes>
</webServices>
26 голосов
/ 23 января 2015

Не уверен, почему все возня с web.config или сериализатором класса. У меня работает следующий код:

XmlSerializer xmlSerializer = new XmlSerializer(myEnvelope.GetType());

using (StringWriter textWriter = new StringWriter())
{
    xmlSerializer.Serialize(textWriter, myEnvelope);
    return textWriter.ToString();
}
21 голосов
/ 19 ноября 2008

Попробуйте Fiddler2 , это позволит вам проверить запросы и ответы. Возможно, стоит отметить, что Fiddler работает с трафиком http и https.

6 голосов
/ 09 апреля 2010

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

Я работаю с клиентом, который разрабатывает веб-сервис вручную с низкоуровневым кодированием. На этом этапе они добавляют свои собственные сообщения об ошибках внутреннего процесса в виде сообщений в формате HTML в ответ ПЕРЕД ответом в формате SOAP. Конечно, автоматическая .Net веб-ссылка взрывает это. Если бы я мог получить необработанный HTTP-ответ после создания исключения, я мог бы искать и анализировать любой SOAP-ответ в смешанном возвращающем HTTP-ответе и знать, что они получили мои данные в порядке или нет.

Позже ...

Вот решение, которое работает, даже после исключения (обратите внимание, что я только после ответа - тоже могу получить запрос):

namespace ChuckBevitt
{
    class GetRawResponseSoapExtension : SoapExtension
    {
        //must override these three methods
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }
        public override object GetInitializer(Type serviceType)
        {
            return null;
        }
        public override void Initialize(object initializer)
        {
        }

        private bool IsResponse = false;

        public override void ProcessMessage(SoapMessage message)
        {
            //Note that ProcessMessage gets called AFTER ChainStream.
            //That's why I'm looking for AfterSerialize, rather than BeforeDeserialize
            if (message.Stage == SoapMessageStage.AfterSerialize)
                IsResponse = true;
            else
                IsResponse = false;
        }

        public override Stream ChainStream(Stream stream)
        {
            if (IsResponse)
            {
                StreamReader sr = new StreamReader(stream);
                string response = sr.ReadToEnd();
                sr.Close();
                sr.Dispose();

                File.WriteAllText(@"C:\test.txt", response);

                byte[] ResponseBytes = Encoding.ASCII.GetBytes(response);
                MemoryStream ms = new MemoryStream(ResponseBytes);
                return ms;

            }
            else
                return stream;
        }
    }
}

Вот как вы можете настроить его в файле конфигурации:

<configuration>
     ...
  <system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="ChuckBevitt.GetRawResponseSoapExtension, TestCallWebService"
           priority="1" group="0" />
      </soapExtensionTypes>
    </webServices>
  </system.web>
</configuration>

"TestCallWebService" должен быть заменен именем библиотеки (так случилось, что это было название приложения консоли тестирования, в котором я работал).

Вам действительно не нужно идти в ChainStream; Вы должны быть в состоянии сделать это проще из ProcessMessage как:

public override void ProcessMessage(SoapMessage message)
{
    if (message.Stage == SoapMessageStage.BeforeDeserialize)
    {
        StreamReader sr = new StreamReader(message.Stream);
        File.WriteAllText(@"C:\test.txt", sr.ReadToEnd());
        message.Stream.Position = 0; //Will blow up 'cause type of stream ("ConnectStream") doesn't alow seek so can't reset position
    }
}

Если вы ищите SoapMessage.Stream, это должен быть поток только для чтения, который вы можете использовать для проверки данных на этом этапе. Это сбой, потому что, если вы действительно читаете поток, то при последующей обработке бомб ошибки не найдены (поток был в конце), и вы не можете сбросить позицию до начала.

Интересно, что если вы используете оба метода, ChainStream и ProcessMessage, метод ProcessMessage будет работать, потому что вы изменили тип потока с ConnectStream на MemoryStream в ChainStream, а MemoryStream разрешает операции поиска. (Я пытался привести ConnectStream к MemoryStream - не разрешено.)

Итак ..... Microsoft должна либо разрешить операции поиска для типа ChainStream, либо сделать SoapMessage.Stream действительно доступной только для чтения копией, как это и должно быть. (Напишите конгрессмену и т. Д.)

Еще один момент. После создания способа получения необработанного ответа HTTP после исключения я все еще не получил полный ответ (как определено анализатором HTTP). Это было связано с тем, что когда веб-служба разработки добавляла сообщения об ошибках HTML в начало ответа, она не корректировала заголовок Content-Length, поэтому значение Content-Length было меньше размера фактического тела ответа. Все, что я получил, это число символов Content-Length, остальные отсутствовали. Очевидно, что когда .Net читает поток ответов, он просто читает число символов Content-Length и не допускает, что значение Content-Length может быть ошибочным. Это так и должно быть; но если значение заголовка Content-Length неверно, единственный способ получить полное тело ответа - использовать снифер HTTP (я использую HTTP Analyzer от http://www.ieinspector.com).

1 голос
/ 09 марта 2009

Я бы предпочел, чтобы инфраструктура выполняла ведение журнала за вас, подключив поток журналирования, который регистрируется как среда, обрабатывающая этот основной поток. Следующее не так чисто, как хотелось бы, поскольку вы не можете выбирать между запросом и ответом в методе ChainStream. Вот как я это делаю. С благодарностью Джону Ханне за превосходную идею потока

public class LoggerSoapExtension : SoapExtension
{
    private static readonly string LOG_DIRECTORY = ConfigurationManager.AppSettings["LOG_DIRECTORY"];
    private LogStream _logger;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }
    public override object GetInitializer(Type serviceType)
    {
        return null;
    }
    public override void Initialize(object initializer)
    {
    }
    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        _logger = new LogStream(stream);
        return _logger;
    }
    public override void ProcessMessage(SoapMessage message)
    {
        if (LOG_DIRECTORY != null)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    _logger.Type = "request";
                    break;
                case SoapMessageStage.AfterSerialize:
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    _logger.Type = "response";
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }
    }
    internal class LogStream : Stream
    {
        private Stream _source;
        private Stream _log;
        private bool _logSetup;
        private string _type;

        public LogStream(Stream source)
        {
            _source = source;
        }
        internal string Type
        {
            set { _type = value; }
        }
        private Stream Logger
        {
            get
            {
                if (!_logSetup)
                {
                    if (LOG_DIRECTORY != null)
                    {
                        try
                        {
                            DateTime now = DateTime.Now;
                            string folder = LOG_DIRECTORY + now.ToString("yyyyMMdd");
                            string subfolder = folder + "\\" + now.ToString("HH");
                            string client = System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Request != null && System.Web.HttpContext.Current.Request.UserHostAddress != null ? System.Web.HttpContext.Current.Request.UserHostAddress : string.Empty;
                            string ticks = now.ToString("yyyyMMdd'T'HHmmss.fffffff");
                            if (!Directory.Exists(folder))
                                Directory.CreateDirectory(folder);
                            if (!Directory.Exists(subfolder))
                                Directory.CreateDirectory(subfolder);
                            _log = new FileStream(new System.Text.StringBuilder(subfolder).Append('\\').Append(client).Append('_').Append(ticks).Append('_').Append(_type).Append(".xml").ToString(), FileMode.Create);
                        }
                        catch
                        {
                            _log = null;
                        }
                    }
                    _logSetup = true;
                }
                return _log;
            }
        }
        public override bool CanRead
        {
            get
            {
                return _source.CanRead;
            }
        }
        public override bool CanSeek
        {
            get
            {
                return _source.CanSeek;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return _source.CanWrite;
            }
        }

        public override long Length
        {
            get
            {
                return _source.Length;
            }
        }

        public override long Position
        {
            get
            {
                return _source.Position;
            }
            set
            {
                _source.Position = value;
            }
        }

        public override void Flush()
        {
            _source.Flush();
            if (Logger != null)
                Logger.Flush();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _source.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _source.SetLength(value);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            count = _source.Read(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
            return count;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _source.Write(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
        }
        public override int ReadByte()
        {
            int ret = _source.ReadByte();
            if (ret != -1 && Logger != null)
                Logger.WriteByte((byte)ret);
            return ret;
        }
        public override void Close()
        {
            _source.Close();
            if (Logger != null)
                Logger.Close();
            base.Close();
        }
        public override int ReadTimeout
        {
            get { return _source.ReadTimeout; }
            set { _source.ReadTimeout = value; }
        }
        public override int WriteTimeout
        {
            get { return _source.WriteTimeout; }
            set { _source.WriteTimeout = value; }
        }
    }
}
[AttributeUsage(AttributeTargets.Method)]
public class LoggerSoapExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1;
    public override int Priority
    {
        get
        {
            return priority;
        }
        set
        {
            priority = value;
        }
    }
    public override System.Type ExtensionType
    {
        get
        {
            return typeof(LoggerSoapExtension);
        }
    }
}
0 голосов
/ 09 октября 2015

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

 Shared Function returnSerializedXML(ByVal obj As Object) As String
    Dim xmlSerializer As New System.Xml.Serialization.XmlSerializer(obj.GetType())
    Dim xmlSb As New StringBuilder
    Using textWriter As New IO.StringWriter(xmlSb)
        xmlSerializer.Serialize(textWriter, obj)
    End Using


    returnSerializedXML = xmlSb.ToString().Replace(vbCrLf, "")

End Function

Просто вызовите функцию, и она вернет строку с сериализованным xml объекта, который вы пытаетесь передать веб-службе (реально, это должно сработать для любого объекта, который вы тоже хотите бросить в него).

В качестве примечания, вызов замены в функции перед возвратом xml должен удалить символы vbCrLf из вывода. У меня их было несколько в сгенерированном xml, но это, очевидно, будет зависеть от того, что вы пытаетесь сериализовать, и я думаю, что они могут быть удалены во время отправки объекта в веб-сервис.

0 голосов
/ 19 ноября 2008

Вы не указали, какой язык используете, но предполагая, что C # / .NET вы можете использовать SOAP-расширения .

В противном случае используйте сниффер, например Wireshark

...