如何将数据作为SoapMessage发送并获取回复?

6
我有一些数据需要以SOAP格式发送到服务器。该服务器将立即确认接收到消息。几个小时后,我会从另一个服务器(可能)接收包含已处理数据信息的SOAP消息。
我阅读了Stackoverflow: How to send SOAP request and receive response。然而,这些答案已经有8年之久了。虽然它们可能仍然有效,但可能存在更新的技术。
实际上,似乎确实如此:Microsoft有System.Web.Services.Protocols,其中包括类如SoapMessage、SoapClientMessage、SoapServerMessage等。
查看这些类,我发现有很多类似SOAP的类(头、扩展、客户端消息、服务器消息……通常提供的示例可以让我了解这些类如何协同工作以及如何使用它们。在MSDN文档中,我只能找到如何处理已有SOAP消息的示例。
给定需要发送的一些数据,我该如何将这些数据包装在这些SOAP类中的一个消息中并发送呢?这些类是为此目的而设计的吗?还是应该坚持2011年的方法,通过自己以soap格式格式化XML数据来创建SOAP Web请求,正如上述Stackoverflow问题所建议的那样?非常抱歉,通常我会写下我尝试过的事情。然而,我不知道提供的SoapMessage类之间的关系。我不知道如何使用它们。补充评论后,我正在使用Windows服务器/Visual Studio(最新版本)/ .NET(最新版本)/ C#(最新版本)。
与服务器的通信是相互认证的。我需要使用PEM(CER / CRT)格式的证书才能与服务器通信。私钥是RSA。该证书由适当的CA颁发,服务器也将使用适当的CA使用的证书。因此,我不需要创建新证书(实际上,它不能被接受)。如果需要,我愿意使用像OpenSsl之类的程序转换证书。
我试图使用Apache TomCat进行通信,但我觉得这对于每天发送一个SOAP消息并等待一天回复的任务来说太过繁琐。
可能是因为Java对我来说是完全新的技术,所以很难看到接收到的消息的内容。所以回到C#和.NET。
我打算创建一个DLL,供控制台应用程序使用。该函数将具有一些数据作为输入的流。它将创建Soap消息,发送它,等待回复以确认消息已正确接收,并等待(可能数小时)包含处理数据结果的新Soap消息。为了进行适当的报告和取消,我想最好使用异步等待来完成此操作。
如果在一个应用程序中无法发送订单并等待结果,我愿意创建一个Windows服务来监听输入,但我更喜欢保持简单。
(虚拟)计算机将仅用于此任务,因此其他人不需要侦听443端口。每天将发送一个订单消息和一个结果消息。

1
只是提醒一下,根据我的经验,使用8年前的技术(如WCF、SOAP等)与新版本的IIS(或者如果你使用.Net Core则是Kestral)不太兼容。如果您可以使用REST,那么它将会更加简单和具有未来性。也许可以询问第三方供应商他们的路线图/计划。 - Jeremy Thompson
1
几个问题
  1. 在您发出网络调用和收到响应之间发生了什么?
  2. 您发送的数据在网络请求中被用来做什么?
- smehaffie
1
你是在为现有的服务器编写客户端还是同时编写客户端和服务器? - Daniel W.
1
我向一个现有的服务器发送执行某项任务的指令。我立即收到回应,确认我的指令已被接受。在这个部分,我是客户端。几个小时后,一些客户端会向我发送包含任务结果的消息。在这个部分,我将成为服务器。我的指令包含足够的信息,以便客户端知道在哪里发送指令的结果,以及如何标识此指令。 - Harald Coppoolse
1
Jeremy:关于更改接口的问题:这是政府与许多公司进行通信的过程。要求更改协议是不可能的。 - Harald Coppoolse
为什么你不让VS为wsdl地址创建所有的方法呢?这真的很容易。 - Amirhossein Mehrvarzi
3个回答

3

我在我的问题中提供了更多信息。希望这能回答你的问题。 - Harald Coppoolse
最简单的正确方法是使用Reactive eXtensions创建一个可观察对象来处理请求、失败、重试策略、调度和测试。创建一个服务并将SOAP客户端调用隐藏在其后面。在现实生活中,您可能会选择使用Quartz作业、Polly或其他100个小工具,但实际上您并不需要它们。证书应安装在调试机器以及部署的虚拟机上。只要记住“混乱的成功胜过完美”,从某个地方开始让它工作,并对其进行改进。 - Margus

3

这里有一份C#控制台客户端和服务器端代码样例(虽然它们在同一示例中,但仅用于演示目的)使用HTTPS。

对于客户端,我们重用SoapHttpClientProtocol类,但是对于服务器端,不幸的是,我们不能重用任何东西,因为类完全绑定到ASP.NET的(IIS)HttpContext类。

对于服务器端,我们使用HttpListener,因此,根据您的配置,服务器端可能需要管理员权限才能调用HttpListenerPrefixes.Add(url)

该代码未使用客户端证书,但可以在我放置// TODO注释的位置添加。

代码假定与使用的url和端口关联的证书。如果没有(使用 netsh http show sslcert 转储所有相关证书),则可以使用此处描述的过程来添加一个:https://dev59.com/Hmgu5IYBdhLWcg3wU1qN#11457719

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml;

namespace SoapTests
{
    class Program
    {
        static void Main(string[] args)
        {
            // code presumes there is an sslcert associated with the url/port below
            var url = "https://127.0.0.1:443/";
            using (var server = new MyServer(url, MyClient.NamespaceUri))
            {
                server.Start(); // requests will occur on other threads
                using (var client = new MyClient())
                {
                    client.Url = url;
                    Console.WriteLine(client.SendTextAsync("hello world").Result);
                }
            }
        }
    }

    [WebServiceBinding(Namespace = NamespaceUri)]
    public class MyClient : SoapHttpClientProtocol
    {
        public const string NamespaceUri = "http://myclient.org/";

        public async Task<string> SendTextAsync(string text)
        {
            // TODO: add client certificates using this.ClientCertificates property
            var result = await InvokeAsync(nameof(SendText), new object[] { text }).ConfigureAwait(false);
            return result?[0]?.ToString();
        }

        // using this method is not recommended, as async is preferred
        // but we need it with this attribute to make underlying implementation happy
        [SoapDocumentMethod]
        public string SendText(string text) => SendTextAsync(text).Result;

        // this is the new Task-based async model (TAP) wrapping the old Async programming model (APM)
        public Task<object[]> InvokeAsync(string methodName, object[] input, object state = null)
        {
            if (methodName == null)
                throw new ArgumentNullException(nameof(methodName));

            return Task<object[]>.Factory.FromAsync(
                beginMethod: (i, c, o) => BeginInvoke(methodName, i, c, o),
                endMethod: EndInvoke,
                arg1: input,
                state: state);
        }
    }

    // server implementation
    public class MyServer : TinySoapServer
    {
        public MyServer(string url, string namespaceUri)
            : base(url)
        {
            if (namespaceUri == null)
                throw new ArgumentNullException(nameof(namespaceUri));

            NamespaceUri = namespaceUri;
        }

        // must be same as client namespace in attribute
        public override string NamespaceUri { get; }

        protected override bool HandleSoapMethod(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement)
        {
            switch (requestMethodElement.LocalName)
            {
                case "SendText":
                    // get the input
                    var text = requestMethodElement["text", NamespaceUri]?.InnerText;
                    text += " from server";

                    AddSoapResult(outputDocument, requestMethodElement, responseMethodElement, text);
                    return true;
            }
            return false;
        }
    }

    // simple generic SOAP server
    public abstract class TinySoapServer : IDisposable
    {
        private readonly HttpListener _listener;

        protected TinySoapServer(string url)
        {
            if (url == null)
                throw new ArgumentNullException(nameof(url));

            _listener = new HttpListener();
            _listener.Prefixes.Add(url); // this requires some rights if not used on localhost
        }

        public abstract string NamespaceUri { get; }
        protected abstract bool HandleSoapMethod(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement);

        public async void Start()
        {
            _listener.Start();
            do
            {
                var ctx = await _listener.GetContextAsync().ConfigureAwait(false);
                ProcessRequest(ctx);
            }
            while (true);
        }

        protected virtual void ProcessRequest(HttpListenerContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            // TODO: add a call to context.Request.GetClientCertificate() to validate client cert
            using (var stream = context.Response.OutputStream)
            {
                ProcessSoapRequest(context, stream);
            }
        }

        protected virtual void AddSoapResult(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement, string innerText)
        {
            if (outputDocument == null)
                throw new ArgumentNullException(nameof(outputDocument));

            if (requestMethodElement == null)
                throw new ArgumentNullException(nameof(requestMethodElement));

            if (responseMethodElement == null)
                throw new ArgumentNullException(nameof(responseMethodElement));

            var result = outputDocument.CreateElement(requestMethodElement.LocalName + "Result", NamespaceUri);
            responseMethodElement.AppendChild(result);
            result.InnerText = innerText ?? string.Empty;
        }

        protected virtual void ProcessSoapRequest(HttpListenerContext context, Stream outputStream)
        {
            // parse input
            var input = new XmlDocument();
            input.Load(context.Request.InputStream);

            var ns = new XmlNamespaceManager(new NameTable());
            const string soapNsUri = "http://schemas.xmlsoap.org/soap/envelope/";
            ns.AddNamespace("soap", soapNsUri);
            ns.AddNamespace("x", NamespaceUri);

            // prepare output
            var output = new XmlDocument();
            output.LoadXml("<Envelope xmlns='" + soapNsUri + "'><Body/></Envelope>");
            var body = output.SelectSingleNode("//soap:Body", ns);

            // get the method name, select the first node in our custom namespace
            bool handled = false;
            if (input.SelectSingleNode("//x:*", ns) is XmlElement requestElement)
            {
                var responseElement = output.CreateElement(requestElement.LocalName + "Response", NamespaceUri);
                body.AppendChild(responseElement);

                if (HandleSoapMethod(output, requestElement, responseElement))
                {
                    context.Response.ContentType = "application/soap+xml; charset=utf-8";
                    context.Response.StatusCode = (int)HttpStatusCode.OK;
                    var writer = new XmlTextWriter(outputStream, Encoding.UTF8);
                    output.WriteTo(writer);
                    writer.Flush();
                    handled = true;
                }
            }

            if (!handled)
            {
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            }
        }

        public void Stop() => _listener.Stop();
        public virtual void Dispose() => _listener.Close();
    }
}

这正是我使用的。之前无法运行的原因是:在我所阅读的所有其他关于HttpListener和安全性的stackoverflow答案中,都没有提到netsh。而你是第一个提到它的人。我使用了正确的Netsh命令,它奏效了!你太棒了! - Harald Coppoolse

0
最简单的现代方法是声明一个简单的类来定义您的消息结构,然后使用HttpClient进行序列化并发送它。
然而,SOAP是为基于描述的消息而建立的标准,因此仍然建议从wsdl描述生成客户端代码,然后使用生成的客户端对象。
我建议像其他人指出的那样,尝试转向REST服务(如果可能的话)。 代码较少复杂,系统使用起来更简单,并且它是全球标准。
以下是两者的比较和示例...

https://smartbear.com/blog/test-and-monitor/understanding-soap-and-rest-basics/


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