Как да регистрирам SOAP съобщения от страна на клиента?

Добър ден. Уча се как да пиша конектор към външна SOAP RCP стилизирана услуга. Използвам плъгина jaxws-maven-plugin за генериране на Java класове от WSDL файла. И използвам Spring, за да създам клиентския компонент на уеб услугата:

@Configuration
public class StoreServiceConfig {

    @Bean
    public StoreWS storeWS() throws IOException {
        JaxWsPortProxyFactoryBean factoryBean = new JaxWsPortProxyFactoryBean();
        factoryBean.setServiceInterface(StoreWS.class);
        factoryBean.setWsdlDocumentUrl(new ClassPathResource("/wsdl/StoreWS.wsdl").getURL());
        factoryBean.setNamespaceUri("urn_store");
        factoryBean.setServiceName("StoreWSService");
        factoryBean.setEndpointAddress("https://10.34.45.82/storeservice/services/StoreWS");
        factoryBean.setUsername("testuser");
        factoryBean.setPassword("testpassword");
        factoryBean.afterPropertiesSet();
        return (StoreWS) factoryBean.getObject();
    }
}

За да тествам клиента, написах тестов клас, използвайки JUnit. Аз наричам клиента така:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = StoreServiceTestConfig.class)
public class StoreServiceImplTest {
    @Autowired
    private StoreWS storeWS;
    ...
    @Test
    public void testExecuteQuery() throws Exception {
        ...
        StoreResponseType response = storeWS.executeQuery(storeRequest);
        ...
    }
}

Сега имам нужда от теста, за да регистрирам пълни изходящи и входящи SOAP съобщения в конзола. Как да го направя, моля? Колкото по-лесно, толкова по-добре.

Намерих съвети да използвам следните системни параметри, но никой от тях не работи за мен:

com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true
com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true
com.sun.xml.ws.transport.local.LocalTransportPipe.dump=true
com.sun.xml.ws.transport.http.HttpAdapter.dump=true

Използвам класове за конфигурация на Spring (без XML) и най-новите версии на всички зависимости.

Намерих този отговор но не знам как да го използвам в моя сценарий.

Много благодаря предварително! Войтех

РЕДАКТИРАНЕ: Моят logback.xml изглежда така, но все още не мога да видя никакви сапунени съобщения в конзолата:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %n
            </Pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
    <logger name="org.springframework.ws.client.MessageTracing">
        <level value="DEBUG" />
        <appender-ref ref="consoleAppender" />
    </logger>
    <logger name="org.springframework.ws.server.MessageTracing">
        <level value="DEBUG" />
        <appender-ref ref="consoleAppender" />
    </logger>
    <root>
        <level value="DEBUG" />
        <appender-ref ref="consoleAppender" />
    </root>
</configuration>

РЕДАКТИРАНЕ 2: pom.xml на родителския проект указва тези зависимости:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.1.2.RELEASE</spring.version>
    <spring-ws-core.version>2.2.0.RELEASE</spring-ws-core.version>
    <slf4j.version>1.7.7</slf4j.version>
    <logback.version>1.1.2</logback.version>
    <junit.version>4.12</junit.version>
    <commons-logging.version>1.1.1</commons-logging.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws-core</artifactId>
            <version>${spring-ws-core.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- LOGGING -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>${commons-logging.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <!-- TESTING -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

pom.xml на модула, в който се намира тестовият клас, зависи от:

<dependencies>
    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>vinweb-connector-ws</artifactId>
    </dependency>
    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>vinweb-connector-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <!-- LOGGING -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
    </dependency>
    <!-- TESTING -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
    </dependency>
</dependencies>

РЕДАКТИРАНЕ 3: Добавих клас LoggingHandler към моя проект:

package storeservice.log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.xml.namespace.QName;
import javax.xml.soap.MimeHeader;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

@Component
public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {

    private final Logger LOG = LoggerFactory.getLogger(LoggingHandler.class);

    @Override
    public Set<QName> getHeaders() {
        return Collections.emptySet();
    }

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        logMessage(context, "SOAP message : ");
        return true;
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        logMessage(context, "SOAP error : ");
        return true;
    }

    @Override
    public void close(MessageContext context) {
    }

    private void logMessage(SOAPMessageContext context, String type) {
        try {
            if(LOG.isDebugEnabled()) {
                Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
                if(outboundProperty) {
                    LOG.debug("Outbound " + type);
                } else {
                    LOG.debug("Inbound " + type);
                }

                SOAPMessage message = context.getMessage();

                // Print out the MIME headers
                MimeHeaders headers = message.getMimeHeaders();
                Iterator<MimeHeader> headersIterator = headers.getAllHeaders();
                MimeHeader mimeHeader;
                LOG.debug("  Mime headers :");
                while(headersIterator.hasNext()) {
                    mimeHeader = headersIterator.next();
                    LOG.debug("    " + mimeHeader.getName() + " : " + mimeHeader.getValue());
                }

                // Print out the message body
                LOG.debug("  Message body :");
                try(OutputStream outStream = new ByteArrayOutputStream()) {
                    message.writeTo(outStream);
                    LOG.debug("    " + outStream.toString());
                }
            }
        } catch (Exception e){
            if(LOG.isErrorEnabled()) {
                LOG.error("Error logging SOAP message", e);
            }
        }
    }
}

И го използвам, за да създам клиентския компонент на уеб услугата:

@Configuration
public class StoreServiceConfig {

    @Autowired
    private LoggingHandler loggingHandler;

    @Bean
    public StoreWS storeWS() throws IOException {
        JaxWsPortProxyFactoryBean factoryBean = new JaxWsPortProxyFactoryBean();
        factoryBean.setServiceInterface(StoreWS.class);
        factoryBean.setWsdlDocumentUrl(new ClassPathResource("/wsdl/StoreWS.wsdl").getURL());
        factoryBean.setNamespaceUri("urn_store");
        factoryBean.setServiceName("StoreWSService");
        factoryBean.setEndpointAddress("https://10.34.45.82/storeservice/services/StoreWS");
        factoryBean.setUsername("testuser");
        factoryBean.setPassword("testpassword");

        factoryBean.setHandlerResolver(handlerResolver());

        factoryBean.afterPropertiesSet();
        return (StoreWS) factoryBean.getObject();
    }

    @Bean
    public HandlerResolver handlerResolver() {
        return new HandlerResolver() {
            @Override
            public List<Handler> getHandlerChain(PortInfo portInfo) {
                List<Handler> handlerChain = new ArrayList<>();
                handlerChain.add(loggingHandler);
                return handlerChain;
            }
        };
    }
}

Резултатът е, че SOAP съобщенията се регистрират, но отговорът на извикването на storeWS.executeQuery е NULL:

StoreResponseType response = storeWS.executeQuery(storeRequest);

Ако коментирам следния ред, той започва да работи отново и на отговора се присвоява обект, но не се регистрират SOAP съобщения:

// factoryBean.setHandlerResolver(handlerResolver());

Открих, че следният ред в класа LoggingHandler причинява, че отговорът на извикване на услугата е нула:

SOAPMessage message = context.getMessage();

person Vojtech    schedule 14.12.2014    source източник


Отговори (2)


Отговорът, за който говорите, изглежда се отнася до базирана на CXF реализация на SOAP клиент. При такъв вид реализация трябва да добавите прехващачи на регистриране към крайната точка. Можете да използвате някаква реализация с Spring, ако искате, и можете да конфигурирате всичко без XML, кажете ми, ако се интересувате.

Във вашия случай можете да промените нивото на регистрационния файл на org.springframework.ws.server.MessageTracing на DEBUG, ако искате да виждате съобщения.

Надявам се това да ви помогне и ви пожелавам добър ден ;).


РЕДАКТИРАНЕ

След известно проучване изглежда, че конфигурацията на регистриране не работи с JaxWsPortProxyFactoryBean. Тази тема в пролетните форуми говорете за същия проблем като вашия. Така че, както е предложено там, можете да създадете свой собствен манипулатор на съобщения и ваша собствена реализация на HandlerResolver, за да регистрирате вашето съобщение. За да тествам това решение, използвам кода logMessage на Logging Handler от docs. oracle.com и създавам нова реализация на HandlerChain, която добавя този LoggingHandler в handlerChain. Накрая зададох този HandlerChain в JaxWsPortProxyFactoryBean.

За да бъдем по-конкретни, ето вашата модифицирана конфигурация:

@Configuration
public class StoreServiceConfig {

    @Bean
    public StoreWS storeWS() throws IOException {
      JaxWsPortProxyFactoryBean factoryBean = new JaxWsPortProxyFactoryBean();
      factoryBean.setServiceInterface(StoreWS.class);
      factoryBean.setWsdlDocumentUrl(new ClassPathResource("/wsdl/StoreWS.wsdl").getURL());
      factoryBean.setNamespaceUri("urn_store");
      factoryBean.setServiceName("StoreWSService");
      factoryBean.setEndpointAddress("https://10.34.45.82/storeservice/services/StoreWS");
      factoryBean.setUsername("testuser");
      factoryBean.setPassword("testpassword");

      factoryBean.setHandlerResolver(handlerResolver());

      factoryBean.afterPropertiesSet();
      return (StoreWS) factoryBean.getObject();
  }

  public HandlerResolver handlerResolver() {
    return new HandlerResolver() {

        public List<Handler> getHandlerChain(PortInfo portInfo) {
            List<Handler> handlerChain = new ArrayList<Handler>();
            handlerChain.add(new LoggingHandler());
            return handlerChain;
        }

    };
 }

}

и класа LoggingHandler:

public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {

  private final org.slf4j.Logger LOG = LoggerFactory
        .getLogger("SOAPMessageLoggingHandler");

  public void close(MessageContext context) {
     }

  public boolean handleFault(SOAPMessageContext context) {
    logMessage(context, "SOAP Error is : ");
    return true;
  }

  public boolean handleMessage(SOAPMessageContext context) {
    logMessage(context, "SOAP Message is : ");
    return true;
  }

  public Set<QName> getHeaders() {
    return Collections.EMPTY_SET;
  }

  private boolean logMessage(MessageContext mc, String type) {
    try {
        if (LOG.isDebugEnabled()) {
            LOG.debug(type);
            SOAPMessage msg = ((SOAPMessageContext) mc)
                    .getMessage();

            // Print out the Mime Headers
            MimeHeaders mimeHeaders = msg.getMimeHeaders();
            Iterator mhIterator = mimeHeaders.getAllHeaders();
            MimeHeader mh;
            String header;
            LOG.debug("  Mime Headers:");
            while (mhIterator.hasNext()) {
                mh = (MimeHeader) mhIterator.next();
                header = new StringBuffer(" Name: ")
                        .append(mh.getName()).append(" Value: ")
                        .append(mh.getValue()).toString();
                LOG.debug(header);
            }

            LOG.debug(" SOAP Message: ");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            msg.writeTo(baos);
            LOG.debug("   " + baos.toString());
            baos.close();
        }

        return true;
    } catch (Exception e) {
        if (LOG.isErrorEnabled()) {
            LOG.error("Error logging SOAP message",
                    e);
        }

        return false;
    }
  }
}

И накрая, какво се отпечатва в конзолата с Logback за съобщението за заявка ... :

    17:29:22 [main] DEBUG SOAPMessageLoggingHandler - SOAP Message is :  
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -   Mime Headers: 
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  Name: Accept Value: text/xml, text/html
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  Name: Content-Type Value: text/xml; charset=utf-8 
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  Name: Content-Length Value: 246 
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  SOAP Message:  
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -    <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><S:Body><ns2:executeQuery xmlns:ns2="org.test"><arg0>test</arg0></ns2:executeQuery></S:Body></S:Envelope> 

... и за съобщението за отговор:

    17:29:22 [main] DEBUG SOAPMessageLoggingHandler - SOAP Message is :  
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -   Mime Headers: 
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  Name: Accept Value: text/xml, text/html
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  Name: Content-Type Value: text/xml; charset=utf-8 
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  Name: Content-Length Value: 266 
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -  SOAP Message:  
    17:29:22 [main] DEBUG SOAPMessageLoggingHandler -    <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><S:Body><ns2:executeQueryResponse xmlns:ns2="org.test"><return>test</return></ns2:executeQueryResponse></S:Body></S:Envelope> 

Мисля, че е по-малко въздействащо, отколкото да замените вашата Spring реализация с CXF. Надявам се този отговор да е по-ефективен от първия ми ^^.

person KevinHol    schedule 14.12.2014
comment
Да, моля, интересувам се от пролетния вариант. Конфигурирах нивото на регистрационния файл на org.springframework.ws.server.MessageTracing и org.springframework.ws.client.MessageTracing на DEBUG (вижте редактиране във въпроса ми), но все още не мога да видя никакви сапунени съобщения в конзолата. Благодаря ти. - person Vojtech; 14.12.2014
comment
Добавили ли сте зависимостта Commons Logging (1.1 или по-висока, според документацията) към вашия класов път? В противен случай можете ли да видите някакъв Spring log, ако поставите root-logger на ниво DEBUG? Това може би е по-скоро проблем с конфигурация/зависимост на регистратор, отколкото проблем с конфигурация на уеб услуга. - person KevinHol; 14.12.2014
comment
Редактирам първия си отговор. Опитайте това решение, преди да проучите конфигурацията за регистриране. - person KevinHol; 14.12.2014
comment
Ако това може да помогне, в моя тестов pom.xml с Logback изключих Commons-logging от зависимостите на Spring, без да добавя другаде. - person KevinHol; 14.12.2014
comment
Много благодаря за съвета. Изглежда, че това може да е правилният начин. Въпреки това имам проблем, моля, вижте моето РЕДАКТИРАНЕ 3. Благодаря ви. - person Vojtech; 15.12.2014
comment
това е най-добрият отговор, който съм виждал, благодаря ви много - person Ethan; 11.11.2020

Манипулатор

public class MyServiceLogHandler implements SOAPHandler<SOAPMessageContext> {

    Logger handlerLog = LogManager.getLogger(MyServiceLogHandler.class);

    public boolean handleMessage(SOAPMessageContext context) {
        SOAPMessage message= context.getMessage();
        boolean isOutboundMessage=  (Boolean)context.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if(isOutboundMessage){
            handlerLog.debug("OUTBOUND MESSAGE\n");

        }else{
            handlerLog.debug("INBOUND MESSAGE\n");
        }

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            message.writeTo(baos);
            handlerLog.debug("   " + baos.toString());
            baos.close();
            return true;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return false;
    }

    public boolean handleFault(SOAPMessageContext context) {
        SOAPMessage message= context.getMessage();
        try {
            message.writeTo(System.out);
        } catch (SOAPException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return false;
    }

    public void close(MessageContext context) {
        // TODO Auto-generated method stub

    }

    public Set<QName> getHeaders() {
        // TODO Auto-generated method stub
        return null;
    }

}

използване

import javax.xml.ws.handler.Handler;
import java.util.List;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;

Binding binding = ((BindingProvider) bookService).getBinding();
        List<Handler> handlerList = binding.getHandlerChain();
        handlerList.add(new MyServiceLogHandler());
        binding.setHandlerChain(handlerList);
person Rajeev    schedule 01.09.2017
comment
Харесва ми този начин за регистриране на манипулатора, но манипулаторът в отговора stackoverflow.com/a/27468517/3165190 има по-хубав изход. - person Falko Menge; 13.06.2018