JAVA 中的 SOAP 请求:无法添加包含 SOAP 命名空间中元素的片段

SOAP Request in JAVA: Cannot add fragments which contain elements which are in the SOAP namespace

我目前正在 Java 中编写一个独立的 soap request/response 应用程序,我正在为此使用以下项目:https://gist.github.com/kdelfour/b2a449a1bac23e3baec8

我正在使用这个样本 WSDL 来开发代码:https://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php?wsdl

从上面的 WSDL 我试图调用操作:LatLonListCityNames 其中有以下肥皂请求:

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ndf="https://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl">
   <soapenv:Header/>
   <soapenv:Body>
      <ndf:LatLonListCityNames soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <displayLevel xsi:type="xsd:integer">1</displayLevel>
      </ndf:LatLonListCityNames>
   </soapenv:Body>
</soapenv:Envelope>

下面是我的代码:

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * This is an example of a simple SOAP Client class to send request body to a
 * SOAP Server.
 *
 * Useful when you want to test a SOAP server and you don't want to generate all
 * SOAP client class from the WSDL.
 *
 * @author kdelfour
 */
public class ASimpleSOAPClient {

    // Default logger
    private static final Logger LOG = Logger.getLogger(ASimpleSOAPClient.class);

    // The SOAP server URI
    private String uriSOAPServer;
    // The SOAP connection
    private SOAPConnection soapConnection = null;

    // If you want to add namespace to the header, follow this constant
    private static final String PREFIX_NAMESPACE = "ndf";
    private static final String NAMESPACE = "https://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl";

    /**
     * A constructor who create a SOAP connection
     *
     * @param url
     *            the SOAP server URI
     */
    public ASimpleSOAPClient(final String url) {
        this.uriSOAPServer = url;

        try {
            createSOAPConnection();
        } catch (UnsupportedOperationException | SOAPException e) {
            LOG.error(e);
        }
    }

    /**
     * Send a SOAP request for a specific operation
     *
     * @param xmlRequestBody
     *            the body of the SOAP message
     * @param operation
     *            the operation from the SOAP server invoked
     * @return a response from the server
     * @throws SOAPException
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public String sendMessageToSOAPServer(String xmlRequestBody, String operation) throws SOAPException, SAXException, IOException,
            ParserConfigurationException {

        // Send SOAP Message to SOAP Server
        final SOAPElement stringToSOAPElement = stringToSOAPElement(xmlRequestBody);
        final SOAPMessage soapResponse = soapConnection.call(createSOAPRequest(stringToSOAPElement, operation), uriSOAPServer);

        // Print SOAP Response
        LOG.info("Response SOAP Message : " + soapResponse.toString());
        return soapResponse.toString();
    }

    /**
     * Create a SOAP connection
     *
     * @throws UnsupportedOperationException
     * @throws SOAPException
     */
    private void createSOAPConnection() throws UnsupportedOperationException,
            SOAPException {

        // Create SOAP Connection
        SOAPConnectionFactory soapConnectionFactory;
        soapConnectionFactory = SOAPConnectionFactory.newInstance();
        soapConnection = soapConnectionFactory.createConnection();
    }

    /**
     * Create a SOAP request
     *
     * @param body
     *            the body of the SOAP message
     * @param operation
     *            the operation from the SOAP server invoked
     * @return the SOAP message request completed
     * @throws SOAPException
     */
    private SOAPMessage createSOAPRequest(SOAPElement body, String operation)
            throws SOAPException {

        final MessageFactory messageFactory = MessageFactory.newInstance();
        final SOAPMessage soapMessage = messageFactory.createMessage();
        final SOAPPart soapPart = soapMessage.getSOAPPart();

        // SOAP Envelope
        final SOAPEnvelope envelope = soapPart.getEnvelope();
        envelope.addNamespaceDeclaration(PREFIX_NAMESPACE, NAMESPACE);

        // SOAP Body
        final SOAPBody soapBody = envelope.getBody();
        soapBody.addChildElement(body);

        // Mime Headers
        final MimeHeaders headers = soapMessage.getMimeHeaders();
        LOG.info("SOAPAction : " + uriSOAPServer + operation);
        headers.addHeader("SOAPAction", uriSOAPServer + operation);

        soapMessage.saveChanges();

        /* Print the request message */
        LOG.info("Request SOAP Message :" + soapMessage.toString());
        return soapMessage;
    }

    /**
     * Transform a String to a SOAP element
     *
     * @param xmlRequestBody
     *            the string body representation
     * @return a SOAP element
     * @throws SOAPException
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException
     */
    private SOAPElement stringToSOAPElement(String xmlRequestBody)
            throws SOAPException, SAXException, IOException,
            ParserConfigurationException {

        // Load the XML text into a DOM Document
        final DocumentBuilderFactory builderFactory = DocumentBuilderFactory
                .newInstance();
        builderFactory.setNamespaceAware(true);
        final InputStream stream = new ByteArrayInputStream(
                xmlRequestBody.getBytes());
        final Document doc = builderFactory.newDocumentBuilder().parse(stream);

        // Use SAAJ to convert Document to SOAPElement
        // Create SoapMessage
        final MessageFactory msgFactory = MessageFactory.newInstance();
        final SOAPMessage message = msgFactory.createMessage();
        final SOAPBody soapBody = message.getSOAPBody();

        // This returns the SOAPBodyElement that contains ONLY the Payload
        return soapBody.addDocument(doc);
    }

    public static void main(String[] args) throws SOAPException, SAXException, IOException, ParserConfigurationException {
        String url = "https://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php?wsdl";
        ASimpleSOAPClient soapClient = new ASimpleSOAPClient(url);
        String xmlMessage = "<soapenv:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ndf=\"https://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl\">\r\n" + 
                "   <soapenv:Header/>\r\n" + 
                "   <soapenv:Body>\r\n" + 
                "      <ndf:LatLonListCityNames soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" + 
                "         <displayLevel xsi:type=\"xsd:integer\">1</displayLevel>\r\n" + 
                "      </ndf:LatLonListCityNames>\r\n" + 
                "   </soapenv:Body>\r\n" + 
                "</soapenv:Envelope>";
        String operation = "LatLonListCityNames";
        soapClient.sendMessageToSOAPServer(xmlMessage, operation);

    }
}

我尝试了 xmlMessage 的排列和组合,但总是以不同的错误结束。 我尝试过的变体之一是:

String xmlMessage = "<soapenv:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n" + 
                "   <soapenv:Header/>\r\n" + 
                "   <soapenv:Body>\r\n" + 
                "      <ndf:LatLonListCityNames soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" + 
                "         <displayLevel xsi:type=\"xsd:integer\">1</displayLevel>\r\n" + 
                "      </ndf:LatLonListCityNames>\r\n" + 
                "   </soapenv:Body>\r\n" + 
                "</soapenv:Envelope>";

在这里,我从信封中删除了 ndf 前缀元素。这给了我一个不同的错误,如下所示:

[Fatal Error] :4:98: The prefix "ndf" for element "ndf:LatLonListCityNames" is not bound.
Exception in thread "main" org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 98; The prefix "ndf" for element "ndf:LatLonListCityNames" is not bound.
    at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
    at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
    at javax.xml.parsers.DocumentBuilder.parse(Unknown Source)
    at ASimpleSOAPClient.stringToSOAPElement(ASimpleSOAPClient.java:159)
    at ASimpleSOAPClient.sendMessageToSOAPServer(ASimpleSOAPClient.java:78)
    at ASimpleSOAPClient.main(ASimpleSOAPClient.java:183)

我不确定我在这里做错了什么。那么这里有什么指示吗?

提前致谢

我也试过下面的代码:

import java.io.*;
import java.net.*;

public class AnotherSoapClient {
    public static void main(String[] args) throws Exception {

        String SOAPUrl      = "https://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php?wsdl";
        String xmlFile2Send = "<soapenv:Envelope xmlns:xsi=\\"http://www.w3.org/2001/XMLSchema-instance\\" xmlns:xsd=\\"http://www.w3.org/2001/XMLSchema\\" xmlns:soapenv=\\"http://schemas.xmlsoap.org/soap/envelope/\\" xmlns:ndf=\\"https://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl\\">\r\n\" + \r\n" + 
                "               \"   <soapenv:Header/>\r\n\" + \r\n" + 
                "               \"   <soapenv:Body>\r\n\" + \r\n" + 
                "               \"      <ndf:LatLonListCityNames soapenv:encodingStyle=\\"http://schemas.xmlsoap.org/soap/encoding/\\">\r\n\" + \r\n" + 
                "               \"         <displayLevel xsi:type=\\"xsd:integer\\">1</displayLevel>\r\n\" + \r\n" + 
                "               \"      </ndf:LatLonListCityNames>\r\n\" + \r\n" + 
                "               \"   </soapenv:Body>\r\n\" + \r\n" + 
                "               \"</soapenv:Envelope>\";\r\n" + 
                "        String operation = \"LatLonListCityNames";

          String SOAPAction = "LatLonListCityNames";

        // Create the connection where we're going to send the file.
        URL url = new URL(SOAPUrl);
        URLConnection connection = url.openConnection();
        HttpURLConnection httpConn = (HttpURLConnection) connection;

        // Open the input file. After we copy it to a byte array, we can see
        // how big it is so that we can set the HTTP Cotent-Length
        // property. (See complete e-mail below for more on this.)

        byte[] b = xmlFile2Send.getBytes();

        // Set the appropriate HTTP parameters.
        httpConn.setRequestProperty( "Content-Length",
                                     String.valueOf( b.length ) );
        httpConn.setRequestProperty("Content-Type","text/xml; charset=utf-8");
        httpConn.setRequestProperty("SOAPAction",SOAPAction);
        httpConn.setRequestMethod( "POST" );
        httpConn.setDoOutput(true);
        httpConn.setDoInput(true);

        // Everything's set up; send the XML that was read in to b.
        OutputStream out = httpConn.getOutputStream();
        out.write( b );    
        out.close();

        // Read the response and write it to standard out.

        InputStreamReader isr =
            new InputStreamReader(httpConn.getInputStream());
        BufferedReader in = new BufferedReader(isr);

        String inputLine;

        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);

        in.close();
    }

  // copy method from From E.R. Harold's book "Java I/O"
  public static void copy(InputStream in, OutputStream out) 
   throws IOException {

    // do not allow other threads to read from the
    // input or write to the output while copying is
    // taking place

    synchronized (in) {
      synchronized (out) {

        byte[] buffer = new byte[256];
        while (true) {
          int bytesRead = in.read(buffer);
          if (bytesRead == -1) break;
          out.write(buffer, 0, bytesRead);
        }
      }
    }
  } 
}

为此我得到以下错误:

Exception in thread "main" java.net.ConnectException: Connection timed out: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
    at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
    at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
    at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
    at java.net.PlainSocketImpl.connect(Unknown Source)
    at java.net.SocksSocketImpl.connect(Unknown Source)
    at java.net.Socket.connect(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.connect(Unknown Source)
    at sun.security.ssl.BaseSSLSocketImpl.connect(Unknown Source)
    at sun.net.NetworkClient.doConnect(Unknown Source)
    at sun.net.www.http.HttpClient.openServer(Unknown Source)
    at sun.net.www.http.HttpClient.openServer(Unknown Source)
    at sun.net.www.protocol.https.HttpsClient.<init>(Unknown Source)
    at sun.net.www.protocol.https.HttpsClient.New(Unknown Source)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(Unknown Source)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(Unknown Source)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(Unknown Source)
    at AnotherSoapClient.main(AnotherSoapClient.java:41)

我可以通过浏览器或 soap-ui 访问 url。我不确定我在这里发生了什么

再次感谢。

您的代码存在多个潜在问题。以后你应该在你的帖子中坚持一个特定的问题 and/or 错误,这样我们才能清楚地回答。

使用 WSDL 实施 SOAP API 可能很棘手,因为涉及的元素太多。关于您的第一个错误示例:“前缀 ndf ... 未绑定”- 此错误意味着您违反了为此 SOAP API 定义的 XML 架构。你不能那样做。您需要 provide/bind ndf 前缀或调整架构。这将修复您的 XML 解析器错误。这个问题很常见,您可以在这里找到更多信息:

xml schema validation error "prefix is not bound"

您的代码导致 ndf 错误的主要问题:这是因为您从以下位置删除了 ndf 架构声明:

<soapenv:Envelope ...

在你的 xmlMessage 中。如果你从那里删除它,你不能在标签名称中包含它,例如你有:

<ndf:LatLonListCityNames soapenv:encodingStyle ...

您可以将所有内容标记到适当的命名空间(ndf、xsd 等)或删除它们。您还可以指示您的代码在解析器中忽略命名空间 problems/validation,但这样您的应用程序可能会在其他地方出现问题。

在提供的 link 中有更多关于此的信息。您还可以从网络搜索中受益 "Java xml namespaces" 并进一步阅读此内容。

关于您的第二个错误示例: 当您的代码尝试连接到位于 Weather.gov 的 SSL/TLS 服务器时,您收到超时错误。您是否一直收到该错误?如果是这样,您需要增加记录器的详细程度,以便您可以看到发生了什么。

我无法修复第一个代码,但是我能够通过按照此答案添加代理配置来修复第二个代码 由于我能够解决我的拦截器问题,因此我将此问题标记为已回答