SpringBoot调用SOAP服务报400错误:如何正确设置请求Payload?
SpringBoot + Java 11调用SOAP Web服务报400错误排查与解决
问题现象
调用SOAP服务时抛出以下错误:
org.springframework.ws.client.WebServiceTransportException: Bad Request [400]
Postman和SOAP UI均可正常调用该服务(返回200状态码),说明问题出在Java代码ProconSoapClient.java中。
Postman成功请求示例
curl --location --request POST 'http://******/ProCONWS/WebService_PCSOrderEntry.asmx' \ --header 'Content-Type: application/soap+xml;charset=UTF-8;action="http://www.procon.dk/calculate_MD5"' \ --header 'Content-Length: 458' \ --data-raw '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:proc="http://www.procon.dk/"> <soap:Header/> <soap:Body> <proc:calculate_MD5> <proc:user>*****</proc:user> <proc:timestamp>20220915 144942</proc:timestamp> <proc:Secret_word>********</proc:Secret_word> </proc:calculate_MD5> </soap:Body> </soap:Envelope>'
报错的Java代码(ProconSoapClient.java)
核心疑问:如何正确设置请求体?objectFactory.createString仅接受String类型,但需要发送CalculateMD5对象。
// request headers Map<String, String> headers = new HashMap<>(); headers.put("Cache-Control", "no-cache"); headers.put("Accept-Encoding", "gzip,deflate"); headers.put("Content-Type", "application/soap+xml;charset=UTF-8;action=\"http://www.procon.dk/calculate_MD5\""); headers.put("Content-Length", "458"); headers.put("Connection", "Keep-Alive"); //headers.put("charset", "utf-8"); WebServiceMessageCallback callback = getRequestCallback(headers); // request object ObjectFactory objectFactory = new ObjectFactory(); CalculateMD5 calculateMD5 = objectFactory.createCalculateMD5(); calculateMD5.setUser(PROCON_SOAP_ACCESS_USER); calculateMD5.setTimestamp(URLEncoder.encode(DateUtil.dateToString(new Date(), "yyyyMMdd HHmmss"), StandardCharsets.UTF_8)); calculateMD5.setSecretWord(PROCON_SOAP_ACCESS_SECRETWORD); // payload final String payload = marshal(calculateMD5); // todo: what must go here? accepts a String but the object is a CalculateMD5 JAXBElement<String> jaxbElementPayload = objectFactory.createString(payload); // call the SOAP service Object response = getWebServiceTemplate().marshalSendAndReceive(PROCON_SOAP_ACCESS_URL, jaxbElementPayload, callback);
生成的类说明
CalculateMD5和ObjectFactory均由WSDL生成。
CalculateMD5.java
/** * <p>Java class for anonymous complex type. * * <p>The following schema fragment specifies the expected content contained within this class. * * <pre> * <complexType> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <sequence> * <element name="user" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/> * <element name="timestamp" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/> * <element name="Secret_word" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/> * </sequence> * </restriction> * </complexContent> * </complexType> * </pre> * * */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "user", "timestamp", "secretWord" }) @XmlRootElement(name = "calculate_MD5") public class CalculateMD5 { protected String user; protected String timestamp; @XmlElement(name = "Secret_word") protected String secretWord; /** * Gets the value of the user property. * * @return * possible object is * {@link String } * */ public String getUser() { return user; } /** * Sets the value of the user property. * * @param value * allowed object is * {@link String } * */ public void setUser(String value) { this.user = value; } /** * Gets the value of the timestamp property. * * @return * possible object is * {@link String } * */ public String getTimestamp() { return timestamp; } /** * Sets the value of the timestamp property. * * @param value * allowed object is * {@link String } * */ public void setTimestamp(String value) { this.timestamp = value; } /** * Gets the value of the secretWord property. * * @return * possible object is * {@link String } * */ public String getSecretWord() { return secretWord; } /** * Sets the value of the secretWord property. * * @param value * allowed object is * {@link String } * */ public void setSecretWord(String value) { this.secretWord = value; } }
ObjectFactory.java
@XmlRegistry public class ObjectFactory { private final static QName _String_QNAME = new QName("http://www.procon.dk/", "string"); /** * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.clubtravel.powwow.soap.client.procon.generated * */ public ObjectFactory() { } /** * Create an instance of {@link CalculateMD5 } * */ public CalculateMD5 createCalculateMD5() { return new CalculateMD5(); } /** * Create an instance of {@link CalculateMD5Response } * */ public CalculateMD5Response createCalculateMD5Response() { return new CalculateMD5Response(); } /** * Create an instance of {@link PcsCreateOrder } * */ public PcsCreateOrder createPcsCreateOrder() { return new PcsCreateOrder(); } /** * Create an instance of {@link PcsCreateOrderResponse } * */ public PcsCreateOrderResponse createPcsCreateOrderResponse() { return new PcsCreateOrderResponse(); } /** * Create an instance of {@link PcsGetOrderStatus } * */ public PcsGetOrderStatus createPcsGetOrderStatus() { return new PcsGetOrderStatus(); } /** * Create an instance of {@link PcsGetOrderStatusResponse } * */ public PcsGetOrderStatusResponse createPcsGetOrderStatusResponse() { return new PcsGetOrderStatusResponse(); } /** * Create an instance of {@link JAXBElement }{@code <}{@link String }{@code >} * * @param value * Java instance representing xml element's value. * @return * the new instance of {@link JAXBElement }{@code <}{@link String }{@code >} */ @XmlElementDecl(namespace = "http://www.procon.dk/", name = "string") public JAXBElement<String> createString(String value) { return new JAXBElement<String>(_String_QNAME, String.class, null, value); } }
问题分析与解决方案
核心错误点
- 请求体处理逻辑错误:无需手动将
CalculateMD5序列化为字符串再用createString包装,这会导致请求体格式完全不符合SOAP服务预期。Spring WebServiceTemplate支持直接传入JAXB可序列化对象或对应的JAXBElement,框架会自动完成序列化。 - Timestamp编码多余:Postman示例中的timestamp为原始字符串(如
20220915 144942),但代码中使用URLEncoder.encode会将空格转为+,导致参数不匹配。 - 硬编码Content-Length风险:Content-Length应由框架自动计算,硬写固定值会因实际请求体长度变化导致错误。
修正后的代码
// request headers Map<String, String> headers = new HashMap<>(); headers.put("Cache-Control", "no-cache"); headers.put("Accept-Encoding", "gzip,deflate"); headers.put("Content-Type", "application/soap+xml;charset=UTF-8;action=\"http://www.procon.dk/calculate_MD5\""); headers.put("Connection", "Keep-Alive"); // 移除硬编码的Content-Length,由框架自动处理 WebServiceMessageCallback callback = getRequestCallback(headers); // request object ObjectFactory objectFactory = new ObjectFactory(); CalculateMD5 calculateMD5 = objectFactory.createCalculateMD5(); calculateMD5.setUser(PROCON_SOAP_ACCESS_USER); // 移除URLEncoder.encode,直接传入原始格式的时间字符串 calculateMD5.setTimestamp(DateUtil.dateToString(new Date(), "yyyyMMdd HHmmss")); calculateMD5.setSecretWord(PROCON_SOAP_ACCESS_SECRETWORD); // 直接使用CalculateMD5对象(因该类带有@XmlRootElement注解,WebServiceTemplate可直接处理) // 若需使用JAXBElement,可手动构建(ObjectFactory未生成对应create方法属于WSDL生成小问题) JAXBElement<CalculateMD5> jaxbElementPayload = new JAXBElement<>( new QName("http://www.procon.dk/", "calculate_MD5"), CalculateMD5.class, calculateMD5 ); // 调用SOAP服务 - 可直接传入calculateMD5或jaxbElementPayload Object response = getWebServiceTemplate().marshalSendAndReceive(PROCON_SOAP_ACCESS_URL, calculateMD5, callback); // 或使用jaxbElementPayload: // Object response = getWebServiceTemplate().marshalSendAndReceive(PROCON_SOAP_ACCESS_URL, jaxbElementPayload, callback);
额外检查点
- 确认
getRequestCallback方法正确设置所有请求头,未覆盖或遗漏Content-Type中的action参数。 - 开启Spring WS日志,对比实际发送的SOAP请求与Postman请求内容,确保格式一致。可在
application.properties中添加:logging.level.org.springframework.ws.client.MessageTracing.sent=TRACE logging.level.org.springframework.ws.server.MessageTracing.received=TRACE
内容的提问来源于stack exchange,提问作者Richard
相关产品推荐
相关产品推荐

