ONVIF WSDL Based JAX-WS SOAP Web Services - Client is SOAP 1.2 but Server is SOAP 1.1

338 Views Asked by At

Issue:

I have created ONVIF WSDL Based Java Client and Server using JAX-WS.

The problem is that the automatically generated Client is using SOAP 1.2 with HTTP "Content-Type: application:/xml+soap" while the Server is exposing SOAP 1.1 with HTTP "Content-Type: text/xml"

The result is that client fails with media type exception and server throws "Unsupported Content-Type" exception:

Jan. 03, 2023 3:42:55 PM com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit handle
SEVERE: Unsupported Content-Type: application/soap+xml; charset=utf-8;action="http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation" Supported ones are: [text/xml]
com.sun.xml.ws.server.UnsupportedMediaException: Unsupported Content-Type: application/soap+xml; charset=utf-8;action="http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation" Supported ones are: [text/xml]

I have confirmed this by doing direct SOAP invocations via curl.

This is successful when "Content-Type: text/xml":

$ curl --verbose  http://127.0.0.1:9080/onvif/device_service -H "Content-Type: text/xml" --data '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><getDeviceInformation xmlns="http://www.onvif.org/ver10"></getDeviceInformation></s:Body></s:Envelope>' | xmllint --format -
*   Trying 127.0.0.1:9080...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 127.0.0.1 (127.0.0.1) port 9080 (#0)
> POST /onvif/device_service HTTP/1.1
> Host: 127.0.0.1:9080
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Type: text/xml
> Content-Length: 273
> 
} [273 bytes data]
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 02 Jan 2023 06:14:40 GMT
< Content-Type: text/xml;charset=utf-8
< Transfer-Encoding: chunked
< Server: Jetty(10.0.12)
< 
{ [114 bytes data]
100  1037    0   764  100   273  31538  11269 --:--:-- --:--:-- --:--:-- 45086
* Connection #0 to host 127.0.0.1 left intact
<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
  <S:Body>
    <ns3:getDeviceInformationResponse xmlns:ns3="http://www.onvif.org/ver10" xmlns:ns4="http://www.onvif.org/ver10/schema" xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:ns6="http://docs.oasis-open.org/wsn/b-2" xmlns:ns7="http://www.w3.org/2005/08/addressing" xmlns:ns8="http://docs.oasis-open.org/wsrf/bf-2" xmlns:ns9="http://docs.oasis-open.org/wsn/t-1" xmlns:ns10="http://www.w3.org/2004/08/xop/include" xmlns:ns11="http://www.onvif.org/ver10/device/wsdl" xmlns:ns12="http://www.w3.org/2003/05/soap-envelope">
      <arg0>john</arg0>
      <arg1>beta</arg1>
      <arg2>0.0.1</arg2>
      <arg3>1</arg3>
      <arg4>hw1</arg4>
    </ns3:getDeviceInformationResponse>
  </S:Body>
</S:Envelope>

And fails when "Content-Type: application/xml+soap":

$ curl --verbose  http://127.0.0.1:9080/onvif/device_service -H "Content-Type: application/xml+soap" --data '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><getDeviceInformation xmlns="http://www.onvif.org/ver10"></getDeviceInformation></s:Body></s:Envelope>' | xmllint --format -
*   Trying 127.0.0.1:9080...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 127.0.0.1 (127.0.0.1) port 9080 (#0)
> POST /onvif/device_service HTTP/1.1
> Host: 127.0.0.1:9080
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Type: application/xml+soap
> Content-Length: 273
> 
} [273 bytes data]
* Mark bundle as not supporting multiuse
< HTTP/1.1 415 Unsupported Media Type
< Date: Mon, 02 Jan 2023 06:19:50 GMT
< Transfer-Encoding: chunked
< Server: Jetty(10.0.12)
< 
{ [5 bytes data]
100   273    0     0  100   273      0   5812 --:--:-- --:--:-- --:--:--  5934
* Connection #0 to host 127.0.0.1 left intact
-:1: parser error : Document is empty

^

Environment is:

  • Ubuntu 22.04
  • OpenJDK 11
  • Embedded Jetty Version 10 for javax.* generated code
  • Embedded Jetty Version 11 for jakarta.* generated code
  • Maven JAX-WS Plugin: com.sun.xml.ws: jaxws-maven-plugin: 2.3.5 & 3.0.2

JAX-WS Version:

I have tested with both Java EE & Jakarta EE and they both show the same SOAP 1.1/1.2 Asymmetry for client and server

Here is server code which just boiler plate using the generated code where

JaxDeviceImpl device = new JaxDeviceImpl();

Is derived from WSDL JAX-WS generated "Device" Server Interface class (WSDL below):

/**
@what Embedded Jetty JAVAX JAX-WS Device Simulator 
@note: See Eclipse Jetty: Programming Guide
*/


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.eclipse.jetty.server.Server;

import javax.xml.ws.Endpoint;

import onvif_relay.service.JaxDeviceImpl;
import fence.util.ConfigurationData;

public class EmbeddedJettyJaxDevice {
    
  public static void main(String[] args) throws Exception {
    ConfigurationData confData = new ConfigurationData(args);

    String srvPort = confData.getItem("onvif-device", "port");
    String port = confData.getItem("onvif-device", "device-port");
    String request = confData.getItem("onvif-device", "request");
    
    try {
        
      System.out.println("Starting the Jetty server on port: " + srvPort + " onvif mgt on: " + port);
        
      Server server = new Server(Integer.parseInt(srvPort));
      
      JaxDeviceImpl device = new JaxDeviceImpl();
      
      // System.setProperty("com.sun.net.httpserver.HttpServerProvider", "org.eclipse.jetty.http.spi.JettyHttpServerProvider");
      // System.setProperty("jakarta.xml.ws.spi.Provider", "org.eclipse.jetty.http.spi.JettyHttpServerProvider");
      // System.setProperty("javax.xml.ws.spi.Provider", "org.eclipse.jetty.http.spi.JettyHttpServerProvider");
     
      String uri = "http://127.0.0.1:" + port + request;
      // Endpoint ep = Endpoint.create(uri, device);
      Endpoint ep = Endpoint.publish(uri, device);
      
      server.start();
      
      // consoleLoop(DevManager);
      
      server.join();
      System.out.println("Stopped the simple server...");
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

Service WSDL:

<?xml version="1.0" encoding="utf-8" ?>
<wsdl:definitions name="onvif_device"
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap12/"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:tds="http://www.onvif.org/ver10/device/wsdl"
 xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
 targetNamespace="http://www.onvif.org/ver10">

 <wsdl:import namespace="http://www.onvif.org/ver10/device/wsdl"
              location="../ver10/device/wsdl/devicemgmt.wsdl"/>
 <wsdl:import namespace="http://www.onvif.org/ver10/media/wsdl"
              location="../ver10/media/wsdl/media.wsdl"/>

 <wsdl:service name="DeviceService">
  <wsdl:documentation>ONVIF - Device</wsdl:documentation>
   <!-- wsdl:port name="Device" binding="tns:DeviceBinding" -->
   <wsdl:port name="DevicePort" binding="tds:DeviceBinding">
    <soap:address location="http://127.0.0.1/onvif/device_service"/>
   </wsdl:port>
 </wsdl:service>

 <!-- wsdl:service name="MediaService">
     <wsdl:documentation>ONVIF - Media</wsdl:documentation>
   <!- wsdl:port name="Device" binding="tns:MediaBinding" ->
   <wsdl:port name="MediaPort" binding="trt:MediaBinding">
    <soap:address location="http://127.0.0.1/onvif/device_service"/>
   </wsdl:port>
 </wsdl:service -->
</wsdl:definitions>

Full code set for both test client and server is posted via github

Expectation:

Expectation is that automatically generated JAX-WS code would work transparently between Client / Server.

NOTE: The current Jakarta XML Web Services specification (Version 3.0) appears to be ambiguous on SOAP 1.1/1.2 support:

  • "Goals - SOAP 1.2 - Whilst SOAP 1.1 is still widely deployed, it’s expected that services will migrate to SOAP 1.2[3][4] now that it is a W3C Recommendation. Jakarta XML Web Services will add support for SOAP 1.2 whilst requiring continued support for SOAP 1.1."
  • "Non Goals - SOAP Encoding Support - Use of the SOAP encoding is essentially deprecated in the web services community, e.g., the WS-I Basic Profile[8] excludes SOAP encoding. Instead, literal usage is preferred, either in the RPC or document style. SOAP 1.1 encoding is supported in JAX-RPC 1.0 and Jakarta XML RPC but its support in Jakarta XML Web Services runs counter to the goal of delegation of data binding to Jakarta XML Binding. Therefore Jakarta XML Web Services will make support for SOAP 1.1 encoding optional and defer description of it to Jakarta XML RPC. Support for the SOAP 1.2 Encoding[4] is optional in SOAP 1.2 and Jakarta XML Web Services will not add support for SOAP 1.2 encoding.

What I tried:

Tried using:

  1. full Jakarta EE toolset
  2. transitional Java EE toolset

Testing with generated Client / Server and via direct curl invocation.

Always get the same result of client (SOAP 1.2) and Server (SOAP 1.1).

NOTE #1: as the underlying WSDL is from ONVIF I do not have option of changing WSDL,

Questions:

  1. Can I change client behavior to always request via SOAP 1.1 ?
  2. Is the problem with Jetty Embedded Server and SOAP 1.2 support ?
  3. Is the problem with Jakarta Reference Implementation ?
1

There are 1 best solutions below

0
zebity On

sDoing further investigation on this I found that the ONVIF SOAP Binding URL:http://schemas.xmlsoap.org/soap/http get redirected to dead end.

Here is snippet from devicemgmt.wsdl

<wsdl:binding name="DeviceBinding" type="tds:Device">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="GetServices">
                <soap:operation soapAction="http://www.onvif.org/ver10/device/wsdl/GetServices"/>
                <wsdl:input>
                        <soap:body use="literal"/>
                </wsdl:input>
                <wsdl:output>
                        <soap:body use="literal"/>
                </wsdl:output>
        </wsdl:operation>

I am not sure if this is root cause of problem, but found that I could work around it by changing the server EndPoint create/publish code to it explicitly used SOAP 1.2 / HTTP binding:

  String soapver = SOAPBinding.SOAP11HTTP_BINDING;
  if (ver.equals("12"))
    soapver = SOAPBinding.SOAP12HTTP_BINDING;
  
  String uri = "http://127.0.0.1:" + port + request;
  Endpoint ep = Endpoint.create(soapver, device);
  ep.publish(uri);

I have committed update to example and confirmed that ONVIF Device Simulator is now serving SOAP 1.2/HTTP as per ONVIF Specification.