Отримання даних RAW-мила від клієнта веб-довідника, що працює на ASP.net


95

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

До чого мені потрібно дістатися, це RAW SOAP Messages (Запит та відповіді)

Який найкращий спосіб зробити це?

Відповіді:


135

Я вніс наступні зміни, 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>

2
Це не працює для мене у 02/2012 за допомогою VS 2010. Хтось знає більш сучасне рішення?
qxotk

2
Це корисно. Однак у моєму випадку відповіді Gzipped, і витягування або ASCII, або шістнадцяткових кодів із комбінованого виводу - це біль. Чи є їх альтернативні способи виводу двосторонніми чи лише текстовими?

2
@Dediqated - ви можете встановити шлях до файлу trace.log, відрегулювавши значення атрибута initializeData у прикладі вище.
DougCouto

3
Це вже ні до чого, я використовував wireshark для перегляду вихідних даних SOAP. Але все одно спасибі!
Виведено

7
Добре. Але XML для мене такий: System.Net Verbose: 0: [14088] 00000000: 3C 3F 78 6D 6C 20 76 65-72 73 69 6F 6E 3D 22 31: <? Xml version = "1 System.Net Verbose: 0: [14088] 00000010: 2E 30 22 20 65 6E 63 6F-64 69 6E 67 3D 22 75 74: .0 "encoding =" ut ....... Будь-яка ідея, як її відформатувати або мати лише дані XML .?
Хабіб

35

Ви можете реалізувати 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>

Я спробую. Я розглядав можливості розширення мила, але мені здавалося, що це більше стосується розміщення послуги. Дасть вам галочку, якщо це спрацює :)
Ендрю Гаррі

Так, це спрацює для реєстрації дзвінків, здійснених із вашої веб-програми, через згенерований проксі.
Джон Лемп,

Це шлях, яким я пішов, він працює дуже добре, і я можу використовувати xpath для маскування будь-яких конфіденційних даних, перш ніж реєструвати їх.
Дейв Багданов

Мені потрібно було додати заголовки до запиту на мило на клієнті. Це мені допомогло. rhyous.com/2015/04/29/…
Rhyous

Я також новачок у c # і не знаю, що вказати для імені Асамблеї. Здається, розширення не працює.
Коллін Андерсон,

28

Не знаю, чому вся суєта з web.config або класом серіалізатора. Для мене працював наведений нижче код:

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

using (StringWriter textWriter = new StringWriter())
{
    xmlSerializer.Serialize(textWriter, myEnvelope);
    return textWriter.ToString();
}

17
Звідки береться myEnvelope?
ProfK

6
Я не розумію, чому ця відповідь не має більше голосів, прямо і чітко! Молодці!
Батіста

1
myEnvalope - це об’єкт, який ви надсилаєте через веб-службу. Я не міг змусити це працювати так, як це описано (зокрема, функція textWriter.tostring), але воно працювало досить добре для моїх цілей у режимі налагодження, так що ви можете бачити необроблений xml після того, як ви зателефонуєте серіалізувати. Дуже цінуємо цю відповідь, оскільки деякі інші працювали зі змішаними результатами (реєстрація за допомогою web.config, наприклад, повертала лише частину xml, і аналізувати її теж було непросто).
user2366842

@Bimmerbound: чи це спрацює із захищеними мильними повідомленнями, що надсилаються через зашифрований VPN?
Наш чоловік у бананах

6
Ця відповідь не дає мильного конверта, він просто серіалізується до xml. Як отримати запит на мильний конверт?
Алі Карача

21

Спробуйте Fiddler2, він дозволить вам перевірити запити та відповіді. Можливо, варто зазначити, що Fiddler працює як з http, так і з https-трафіком.


Це зашифрована послуга https
Ендрю Гаррі,

8
Скрипаль може розшифрувати https-трафік
Аарон Фішер,

1
Я знайшов це рішення кращим, ніж додавання трасування в web / app.config. Дякую!
andyuk

1
Але використання system.diagnostics у файлі конфігурації не вимагає встановлення сторонніх програм
Azat

1
@AaronFischer: Чи буде Скрипаль працювати з мильними повідомленнями, надісланими через зашифровану VPN?
Наш чоловік у бананах

6

Схоже, рішення Тіма Картера не працює, якщо виклик веб-посилання викликає виняток. Я намагався отримати неопрацьований веб-резонанс, щоб я міг перевірити його (у коді) в обробнику помилок, коли буде вилучено виняток. Однак я виявляю, що журнал відповідей, написаний методом Тіма, порожній, коли виклик викликає виняток. Я не повністю розумію код, але, схоже, метод Тіма врізається в процес після того моменту, коли .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 sniffer). Це було тому, що коли веб-служба розробки додала повідомлення про помилки HTML на початок відповіді, вона не відрегулювала заголовок Content-Length, тому значення Content-Length було менше розміру фактичного тіла відповіді. Все, що я отримав, - це кількість знаків Content-Length, кількість інших символів відсутня. Очевидно, що коли .Net читає потік відповідей, він просто зчитує кількість символів Content-Length і не допускає помилки значення Content-Length. Це як слід; але якщо значення заголовка Content-Length неправильне, єдиний спосіб, яким ви коли-небудь отримаєте все тіло відповіді, - це HTTP-sniffer (I user HTTP Analyzer fromhttp://www.ieinspector.com ).


2

Я вважаю за краще, щоб фреймворк виконував реєстрацію за вас, підключаючи до потоку реєстрації, який реєструється як фреймворк, який обробляє цей потік. Далі не так чисто, як мені хотілося б, оскільки ви не можете визначитися між запитом та відповіддю у методі 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);
        }
    }
}

1
@TimCarter мені це вдалося, єдине, що я видалив, - це клієнтська частина, яка дає мені ім’я з двокрапкою, що спричиняє виняток. Отже, як тільки я видалю цей рядок, він чудово працює для тих, хто хоче мати один файл для кожного запиту / відповіді. Єдине, що потрібно додати, це щось очевидне, якщо ви прочитаєте інші відповіді, і це те, що вам потрібно додати вузол web.config, щоб вказати soapExtension. Дякую!
netadictos

1

Ось спрощена версія основної відповіді. Додайте це до <configuration>елемента вашого web.configабо App.configфайлу. Він створить trace.logфайл у bin/Debugпапці вашого проекту . Або ви можете вказати абсолютний шлях до файлу журналу, використовуючи initializeDataатрибут.

  <system.diagnostics>
    <trace autoflush="true"/>
    <sources>
      <source name="System.Net" maxdatasize="9999" tracemode="protocolonly">
        <listeners>
          <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net" value="Verbose"/>
    </switches>
  </system.diagnostics>

Він попереджає, що атрибути maxdatasizeand tracemodeне дозволяються, але вони збільшують обсяг даних, які можна реєструвати, і уникають реєстрації всього в шістнадцятковій системі.


0

Ви не вказали, якою мовою ви користуєтесь, але припускаючи, що ви можете використовувати C # / .NET розширення SOAP .

В іншому випадку скористайтеся носієм, таким як Wireshark


1
Так, спробував wireshark, він може показати мені лише інформацію заголовка, вміст мила зашифрований.
Ендрю Гаррі,

Це можливо тому, що ви використовуєте https. Розширення SOAP, наскільки я пам’ятаю, працюють на більш високому рівні, тому ви зможете бачити дані після їх розшифрування.
rbrayb

2
Використовуйте Fiddler замість Wireshark, він розшифровує https з коробки.
Родріго Штраус

-1

Я усвідомлюю, що я досить запізнився на вечірку, і оскільки мова насправді не була вказана, ось рішення 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 об'єкта, який ви намагаєтесь передати веб-службі (насправді, це повинно працювати для будь-якого об'єкта, на який ви також хочете).

Як додаткове зауваження, виклик replace у функції перед поверненням xml полягає у видаленні символів vbCrLf з виводу. У моїх була купа з них у згенерованому xml, однак це, очевидно, буде змінюватися залежно від того, що ви намагаєтесь серіалізувати, і я думаю, що вони можуть бути видалені під час надсилання об'єкта до веб-служби.


Той, хто вдарив мене голосом проти, не соромтеся повідомити мені, чого я втрачаю / що можна покращити.
user2366842
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.