[asp.net] ASP.net에서 실행되는 웹 참조 클라이언트에서 RAW Soap 데이터 가져 오기

현재 프로젝트에서 웹 서비스 클라이언트 문제를 해결하려고합니다. 서비스 서버 (대부분 LAMP)의 플랫폼을 잘 모르겠습니다. 나는 내 고객과의 잠재적 인 문제를 제거했기 때문에 울타리의 측면에 결함이 있다고 생각합니다. 클라이언트는 서비스 WSDL에서 자동 생성 된 표준 ASMX 유형 웹 참조 프록시입니다.

필요한 것은 RAW SOAP 메시지 (요청 및 응답)입니다.

이것에 대해 가장 좋은 방법은 무엇입니까?



답변

web.configSOAP (Request / Response) Envelope를 얻기 위해 다음과 같이 변경했습니다 . 이렇게하면 모든 원시 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>


답변

전체 요청 및 응답을 로그 파일에 기록하는 SoapExtension을 구현할 수 있습니다. 그런 다음 web.config에서 SoapExtension을 활성화하여 디버깅 목적으로 쉽게 켜고 끌 수 있습니다. 다음은 내가 사용하기 위해 찾아서 수정 한 예입니다. 제 경우에는 로깅이 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); }
    }
}

그런 다음 YourNamespace 및 YourAssembly가 SoapExtension의 클래스 및 어셈블리를 가리키는 web.config에 다음 섹션을 추가합니다.

<webServices>
  <soapExtensionTypes>
    <add type="YourNamespace.SoapLoggerExtension, YourAssembly"
       priority="1" group="0" />
  </soapExtensionTypes>
</webServices>


답변

web.config 또는 serializer 클래스가 왜 모든 소란 스러운지 잘 모르겠습니다. 아래 코드가 저에게 효과적이었습니다.

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

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


답변

시도 Fiddler2를 당신이 요청 및 응답을 검사하게됩니다. Fiddler가 http 및 https 트래픽 모두에서 작동한다는 점은 주목할 가치가 있습니다.


답변

웹 참조 호출에서 예외가 발생하면 Tim Carter의 솔루션이 작동하지 않는 것 같습니다. 예외가 발생하면 오류 처리기에서 코드로 검사 할 수 있도록 원시 웹 공명을 얻으려고 노력했습니다. 그러나 호출이 예외를 throw 할 때 Tim의 메서드에 의해 작성된 응답 로그가 비어 있음을 발견했습니다. 나는 코드를 완전히 이해하지 못하지만 Tim의 방법은 .Net이 이미 웹 응답을 무효화하고 폐기 한 지점 이후에 프로세스를 중단하는 것으로 보입니다.

저수준 코딩으로 웹 서비스를 수동으로 개발하는 클라이언트와 함께 일하고 있습니다. 이 시점에서 그들은 자체 내부 프로세스 오류 메시지를 HTML 형식의 메시지로 SOAP 형식의 응답 전에 응답에 추가합니다. 물론, automagic .Net 웹 참조는 이것에 대해 폭발합니다. 예외가 발생한 후 원시 HTTP 응답을 얻을 수 있다면 혼합 반환 HTTP 응답 내에서 SOAP 응답을 찾아 구문 분석하고 내 데이터를 수신했는지 여부를 알 수 있습니다.

나중 …

다음은 실행 후에도 작동하는 솔루션입니다 (응답 후에 만 ​​수행됩니다. 요청도받을 수 있음).

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 방법을 모두 수행하면 ChainStream에서 스트림 유형을 ConnectStream에서 MemoryStream으로 변경하고 MemoryStream에서 검색 작업을 허용하기 때문에 ProcessMessage 메서드가 작동합니다. (ConnectStream을 MemoryStream으로 캐스팅하려고 시도했지만 허용되지 않았습니다.)

그래서 ….. Microsoft는 ChainStream 유형에 대한 검색 작업을 허용하거나 SoapMessage.Stream이 예상대로 읽기 전용 복사본으로 만들어야합니다. (의원 등을 쓰십시오.)

한 가지 더. 예외가 발생한 후 원시 HTTP 응답을 검색하는 방법을 만든 후에도 여전히 전체 응답을 얻지 못했습니다 (HTTP 스니퍼에 의해 결정됨). 이는 개발 웹 서비스가 응답 시작 부분에 HTML 오류 메시지를 추가했을 때 Content-Length 헤더를 조정하지 않았기 때문에 Content-Length 값이 실제 응답 본문의 크기보다 작기 때문입니다. 내가 얻은 것은 Content-Length 값 문자 수뿐이었습니다. 나머지는 누락되었습니다. 분명히 .Net이 응답 스트림을 읽을 때 Content-Length 문자 수를 읽고 Content-Length 값이 잘못 될 가능성을 허용하지 않습니다. 이것은 당연한 것입니다. 그러나 Content-Length 헤더 값이 잘못된 경우 전체 응답 본문을 얻을 수있는 유일한 방법은 HTTP 스니퍼를 사용하는 것입니다 (저는http://www.ieinspector.com ).


답변

프레임 워크가 기본 스트림을 처리 할 때 로깅하는 로깅 스트림에 연결하여 프레임 워크가 로깅을 수행하도록하는 것이 좋습니다. 다음은 ChainStream 메서드에서 요청과 응답 사이를 결정할 수 없기 때문에 내가 원하는만큼 깨끗하지 않습니다. 다음은 내가 처리하는 방법입니다. 스트림 아이디어를 재정의 한 Jon Hanna에게 감사드립니다.

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);
        }
    }
}


답변

다음은 상위 답변의 단순화 된 버전입니다. 이것을 또는 파일 의 <configuration>요소에 추가 하십시오. 프로젝트 폴더에 파일 이 생성됩니다 . 또는 속성을 사용하여 로그 파일의 절대 경로를 지정할 수 있습니다 .web.configApp.configtrace.logbin/DebuginitializeData

  <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>

maxdatasizetracemode속성은 허용되지 않는다고 경고 하지만 기록 할 수있는 데이터의 양을 늘리고 모든 것을 16 진수로 기록하지 않도록합니다.