I am trying to send a soap request and keep getting HTTP response 411 error because of larger size of soap request. In most of the cases soap request length is more that 8k.
ERROR MESSAGE
2020-02-27 08:26:09,618 WARNING [100] [org.apache.cxf.phase.PhaseInterceptorChain] (my-thread-1) Interceptor for {http://example.com}CreationService#{http://cxf.apache.org/jaxws/dispatch}Invoke has thrown exception, unwinding now: org.apache.cxf.interceptor.Fault: Could not send Message.
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:64) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:514) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:423) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:324) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:277) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invokeWrapped(ClientImpl.java:312) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:327) [cxf-rt-frontend-jaxws-3.1.6.jar:3.1.6]
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:246) [cxf-rt-frontend-jaxws-3.1.6.jar:3.1.6]
...
Caused by: org.apache.cxf.transport.http.HTTPException: HTTP response '411: Length Required' when communicating with http://192.100.110.17:8504/example/services/CreationREQ
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.doProcessResponseCode(HTTPConduit.java:1600) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1607) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1551) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1348) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.io.CacheAndWriteOutputStream.postClose(CacheAndWriteOutputStream.java:56) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.io.CachedOutputStream.close(CachedOutputStream.java:216) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:651) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62) [cxf-core-3.1.6.jar:3.1.6]
... 84 more
2020-02-27 08:26:09,621 ERROR [100] [org.jboss.as.ejb3.invocation] (my-thread-1) JBAS014134: EJB invocation failed on DaoFacade component for method public abstract void com.example.addon.core.dao.facade.DaoFacadeInterface.invokeExternalService(com.example.db.models.Synchronizable) throws com.example.addon.addon.SOAPException: javax.ejb.EJBException: javax.xml.ws.WebServiceException: Could not send Message.
...
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [rt.jar:1.7.0_79]
at java.lang.Thread.run(Thread.java:745) [rt.jar:1.7.0_79]
Caused by: javax.xml.ws.WebServiceException: Could not send Message.
at org.apache.cxf.jaxws.DispatchImpl.mapException(DispatchImpl.java:272) [cxf-rt-frontend-jaxws-3.1.6.jar:3.1.6]
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:334) [cxf-rt-frontend-jaxws-3.1.6.jar:3.1.6]
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:246) [cxf-rt-frontend-jaxws-3.1.6.jar:3.1.6]
...
at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:288) [jboss-invocation-1.1.2.Final-redhat-1.jar:1.1.2.Final-redhat-1]
at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:280) [jboss-as-ejb3-7.5.0.Final-redhat-21.jar:7.5.0.Final-redhat-21]
... 40 more
Caused by: org.apache.cxf.transport.http.HTTPException: HTTP response '411: Length Required' when communicating with http://192.100.110.17:8504/example/services/CreationREQ
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.doProcessResponseCode(HTTPConduit.java:1600) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1607) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1551) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1348) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.io.CacheAndWriteOutputStream.postClose(CacheAndWriteOutputStream.java:56) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.io.CachedOutputStream.close(CachedOutputStream.java:216) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:651) [cxf-rt-transports-http-3.1.6.jar:3.1.6]
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:514) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:423) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:324) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:277) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.endpoint.ClientImpl.invokeWrapped(ClientImpl.java:312) [cxf-core-3.1.6.jar:3.1.6]
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:327) [cxf-rt-frontend-jaxws-3.1.6.jar:3.1.6]
... 77 more
Here is my soap client code looks like:
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.DispatchImpl;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.log4j.Logger;
public class SOAPClient {
private static final String CXF_RESPONSE_CODE = "org.apache.cxf.message.Message.RESPONSE_CODE";
private static final String JAXRS_RESPONSE_CODE = "javax.xml.ws.http.response.code";
private static Logger logger = Logger.getLogger(SOAPClient.class);
/**
* Nested class only accessible after {#link SOAPClient#builder()} method call
* <p>
* It provides a Fluent Interface to make the client code more readable.
*/
public static class Builder {
private String endpoint;
private String namespace;
private String serviceName;
private Boolean basicAuthentication = FALSE;
private String username;
private String password;
private Long connectionTimeout;
private Long receiveTimeout;
private String portName;
private String soapAction;
private StreamSource soapRequest;
private OutputStream soapResponse;
private Dispatch<SOAPMessage> dispatch;
private SOAPMessage requestSOAPMessage;
private SOAPMessage returnedSOAPMessage;
public Builder endpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}
public Builder namespace(String namespace) {
this.namespace = namespace;
return this;
}
public Builder serviceName(String serviceName) {
this.serviceName = serviceName;
return this;
}
public Builder portName(String portName) {
this.portName = portName;
return this;
}
public Builder soapAction(String soapAction) {
this.soapAction = soapAction;
return this;
}
public Builder basicAuthentication(Boolean basicAuthentication) {
this.basicAuthentication = basicAuthentication;
return this;
}
public Builder username(String username) {
this.username = username;
return this;
}
public Builder password(String password) {
this.password = password;
return this;
}
public Builder connectionTimeout(Long connectionTimeout) {
this.connectionTimeout = connectionTimeout;
return this;
}
public Builder receiveTimeout(Long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
return this;
}
public Builder soapRequest(File soapRequest) throws IOException {
if (soapRequest == null)
throw new IllegalStateException("soapRequest not set");
InputStream is = Files.newInputStream(soapRequest.toPath());
this.soapRequest = new StreamSource(is, StandardCharsets.UTF_8.name());
return this;
}
public Builder soapRequest(InputStream soapRequest) {
this.soapRequest = new StreamSource(soapRequest);
return this;
}
public Builder soapRequest(Reader soapRequest) {
this.soapRequest = new StreamSource(soapRequest);
return this;
}
public int execute(File soapResponse) throws IOException, SOAPException, GeneralSecurityException {
if (soapResponse == null)
throw new IllegalStateException("soapResponse not set");
this.soapResponse = Files.newOutputStream(soapResponse.toPath());
return execute();
}
public int execute(OutputStream soapResponse) throws IOException, SOAPException, GeneralSecurityException {
if (soapResponse == null)
throw new IllegalStateException("soapResponse not set");
this.soapResponse = soapResponse;
return execute();
}
private int execute() throws SOAPException, IOException, GeneralSecurityException {
createDispatch();
addBasicAuthentication();
addSoapAction();
addTimeouts();
setSOAPMessage();
returnedSOAPMessage = dispatch.invoke(requestSOAPMessage);
returnedSOAPMessage.writeTo(soapResponse);
return Integer.parseInt(getResponseCode());
}
private String getResponseCode() {
Object responseCode = dispatch.getResponseContext().get(CXF_RESPONSE_CODE);
if (responseCode != null)
return responseCode.toString();
else {
responseCode = dispatch.getResponseContext().get(JAXRS_RESPONSE_CODE);
if (responseCode != null)
return responseCode.toString();
else
return "-1";
}
}
private void createDispatch() {
if (endpoint == null || endpoint.isEmpty())
throw new IllegalStateException("endpoint not set");
QName serviceQName = new QName(namespace, serviceName);
logger.debug("Creating the Service QName, " + serviceQName);
// Add a separate name space for method if required
QName portQName = new QName(namespace, portName);
logger.debug("Creating port QName, " + portQName);
Service serviceRef = Service.create(serviceQName);
serviceRef.addPort(portQName, SOAPBinding.SOAP11HTTP_BINDING, endpoint);
dispatch = serviceRef.createDispatch(portQName, SOAPMessage.class, Service.Mode.MESSAGE);
}
private void addBasicAuthentication() throws GeneralSecurityException {
if (basicAuthentication)
setBasicAuthentication();
}
private void setBasicAuthentication() throws GeneralSecurityException {
if (username == null || username.isEmpty())
throw new IllegalStateException("username not set with BasicAuthentication");
if (password == null || password.isEmpty())
throw new IllegalStateException("password not set with BasicAuthentication");
dispatch.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, username);
dispatch.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, JcodificaLib.decrypt(password));
}
private void addSoapAction() {
if (soapAction != null)
setSoapAction();
}
private void setSoapAction() {
logger.debug("SoapAction:" + soapAction);
dispatch.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, TRUE);
dispatch.getRequestContext().put(BindingProvider.SOAPACTION_URI_PROPERTY, soapAction);
}
private void addTimeouts() {
if (receiveTimeout != null) {
setReceiveTimeout();
}
if (connectionTimeout != null) {
setConnectionTimeout();
}
}
private void setConnectionTimeout() {
logger.debug("connectionTimeout:" + connectionTimeout);
dispatch.getRequestContext().put("javax.xml.ws.client.connectionTimeout", connectionTimeout);
}
private void setReceiveTimeout() {
logger.debug("receiveTimeout:" + receiveTimeout);
dispatch.getRequestContext().put("javax.xml.ws.client.receiveTimeout", receiveTimeout);
}
private void setSOAPMessage() throws SOAPException {
MessageFactory messageFactory = MessageFactory.newInstance();
requestSOAPMessage = messageFactory.createMessage();
SOAPPart soapPart = requestSOAPMessage.getSOAPPart();
soapPart.setContent(soapRequest);
}
}
public static SOAPClient.Builder builder() {
return new SOAPClient.Builder();
}
}
I tried fixing the issue by enabling chunking and setting chunking threshold to 8192 as below, but I am getting the same error message:
private void setHttpClientPolicies() {
final Client client = ((DispatchImpl<SOAPMessage>) dispatch).getClient();
final HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
final HTTPClientPolicy httpClientPolicy = httpConduit.getClient();
httpConduit.setClient(httpClientPolicy);
httpClientPolicy.setAllowChunking(TRUE);
httpClientPolicy.setChunkingThreshold(8192);
}
Any clue how to fix the issue ?
I suspect you may try to set the content length in the header. IE, the size of of your request using Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing.
I would guess that if you correctly identified the size of the request the server might not choke on it. Worth a try.
Content-Length: SIZE OF REQUIRED DATA IN BYTES \n\n
It may require accessing some lower level portions of your request framework.
References:
https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length
I would try the contrary of what you did.
I suspect the server is not supporting chunking. Therefore, you should configure CXF to disable (and not enable!) chunking, like the doc suggests it:
If you are getting strange errors (generally not soap faults, but
other HTTP type errors) when trying to interact with a service, try
turning off chunking to see if that helps.
SOAP request by org.apache.http.client.HttpClient(4.1)
You have to specify data length.
req_xml.length()
// SOAP request(xml) read-in
File req_xml = new File("test/xml/request.xml");
// SOAP request send
HttpPost post = new HttpPost("http://localhost:8080/test/api/");
post.setEntity(new InputStreamEntity(new FileInputStream(req_xml), **req_xml.length()**));
post.setHeader("Content-type", "text/xml; charset=UTF-8");
post.setHeader("SOAPAction", "");
HttpClient client = new DefaultHttpClient();
HttpResponse response = client.execute(post);
// SOAP response(xml) get
String res_xml = EntityUtils.toString(response.getEntity());
Check that working code just supply xml and change web path.
You have whole builder code where I don't understand are you doing post/get.By Default everything is http get.
createDispatch();
addBasicAuthentication();
addSoapAction();
addTimeouts();
setSOAPMessage();
returnedSOAPMessage = dispatch.invoke(requestSOAPMessage);
returnedSOAPMessage.writeTo(soapResponse);
return Integer.parseInt(getResponseCode());
Answering my own question.
The answers posted here are pointing correctly that by disabling chunking or adding Content-Length the issue can be resolved. But my challenge was that I wasn't able to disable the chunking as it can be seen in my question description.
So I was trying to add Content-Length to http header and as you can see I'm not using any Apache library to write my soap client code so it was difficult to figure out how to add the Content-Length to the HTTP Header. Lastly I found a way to do so. The code snippet can be found below:
private void setSOAPMessage() throws SOAPException {
MessageFactory messageFactory = MessageFactory.newInstance();
requestSOAPMessage = messageFactory.createMessage();
SOAPPart soapPart = requestSOAPMessage.getSOAPPart();
soapPart.setContent(soapRequest);
// Added Content-Length to HTTP Header
Map<String, List<String>> requestHeaderMap = new HashMap<String, List<String>>();
requestHeaderMap.put("Content-Length", Collections.singletonList(String.valueOf(soapRequestXMLSize)));
dispatch.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, requestHeaderMap);
}
Here the soapRequestXMLSize was calculated from the actual soap request xml which is stored as String in my case.
Integer soapRequestSize = soapRequestXML.length();
Related
I am trying to use elastic search in a way that the client would be closed after 1 hour or so I have noticed that in my tests it is closing after 30 minutes and I cannot understand what is the problem, this is how I implemented my ElasticSearch client class:
public class ElasticSearchClient implements Closeable {
public static final String INDEX = EnvConf.getProperty("elastic.tests_report.index");
private static final String HOST = EnvConf.getProperty("elastic.host");
private static final int PORT = EnvConf.getAsInteger("elastic.port");
private final RestHighLevelClient restClient;
public ElasticSearchClient() {
RestClientBuilder builder = RestClient.builder(
new HttpHost(HOST, PORT))
.setRequestConfigCallback(
requestConfigBuilder -> requestConfigBuilder
.setConnectTimeout(5000)
.setSocketTimeout(10000))
.setMaxRetryTimeoutMillis(90000);
restClient = new RestHighLevelClient(builder);
}
public IndexResponse index(String index , String type , XContentBuilder contentBuilder) throws IOException {
IndexRequest indexRequest = new IndexRequest(index, type)
.source(contentBuilder);
return restClient.index(indexRequest , RequestOptions.DEFAULT);
}
public SearchResponse query(QueryBuilder queryBuilder, int maxHits, String...indices) throws IOException {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS))
.size(maxHits)
.sort(new FieldSortBuilder("start_timestamp").order(SortOrder.DESC));
SearchRequest searchRequest = new SearchRequest(indices);
searchRequest.source(searchSourceBuilder.query(queryBuilder));
return restClient.search(searchRequest , RequestOptions.DEFAULT);
}
#Override
public void close() throws IOException {
restClient.close();
}
}
I thought it would be running for 1.5 hour, I saw that after 30 minutes it stops to index the client and this is the error message I get:
java.lang.IllegalStateException: Request cannot be executed; I/O reactor status: STOPPED
at org.apache.http.util.Asserts.check(Asserts.java:46)
at org.apache.http.impl.nio.client.CloseableHttpAsyncClientBase.ensureRunning(CloseableHttpAsyncClientBase.java:90)
at org.apache.http.impl.nio.client.InternalHttpAsyncClient.execute(InternalHttpAsyncClient.java:123)
at org.elasticsearch.client.RestClient.performRequestAsync(RestClient.java:533)
at org.elasticsearch.client.RestClient.performRequestAsyncNoCatch(RestClient.java:516)
at org.elasticsearch.client.RestClient.performRequest(RestClient.java:228)
at org.elasticsearch.client.RestHighLevelClient.internalPerformRequest(RestHighLevelClient.java:1762)
at org.elasticsearch.client.RestHighLevelClient.performRequest(RestHighLevelClient.java:1732)
at org.elasticsearch.client.RestHighLevelClient.performRequestAndParseEntity(RestHighLevelClient.java:1694)
at org.elasticsearch.client.RestHighLevelClient.index(RestHighLevelClient.java:926)
at com.indeni.automation.api.db.ElasticSearchClient.index(ElasticSearchClient.java:46)
at com.indeni.automation.core.runner.testng.TestListener.updateDataSourceWithTestResult(TestListener.java:215)
at com.indeni.automation.core.runner.testng.TestListener.onFinish(TestListener.java:125)
at org.testng.TestRunner.fireEvent(TestRunner.java:1239)
at org.testng.TestRunner.afterRun(TestRunner.java:1030)
at org.testng.TestRunner.run(TestRunner.java:636)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
at org.testng.SuiteRunner.access$000(SuiteRunner.java:39)
at org.testng.SuiteRunner$SuiteWorker.run(SuiteRunner.java:400)
at org.testng.internal.thread.ThreadUtil$2.call(ThreadUtil.java:64)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
I noticed it stopped working after 30 minutes and I can't get the reason.
I want to retrieve Common Name (CN) property from client certificate in SOAP communication. I'm using Spring WebServiceTemplate to create my webservice endpoint. I have already implemented WS mutual authentication following the example.
Is there any solution to obtain certificate details from client request by means of WebServiceTemplate or some other library?
Fortunately, I have managed to figure it out!
Spring WS provides very convenient way to retrieve the X509Certificate.
Normally, You have an endpoint like this:
#Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service";
...
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
#ResponsePayload
public GetCountryResponse getCountry(#RequestPayload GetCountryRequest request) {
//method body here
return response;
}
}
However, Spring allows to add additional parameters the method annotated as #PayloadRoot. It can be a MessageContext instance.
public GetCountryResponse getCountry(#RequestPayload MessageContext context, #RequestPayload GetCountryRequest request)`
Then You will be able to obtain the wsse:Security header as follows:
WebServiceMessage webServiceMessageRequest = context.getRequest();
SaajSoapMessage saajSoapMessage = (SaajSoapMessage) webServiceMessageRequest;
SOAPMessage doc = saajSoapMessage.getSaajMessage();
Element elem = WSSecurityUtil.getSecurityHeader(doc.getSOAPPart(), "");
Now get the right content of BinarySecurityToken tag:
String binarySecurityToken = elem.getElementsByTagName("BinarySecurityToken").item(0).getTextContent();
At the end, you should recreate the X509Certificate by passing binarySecurityToken as its constructor parameter. Later You can extract CN by many different ways for example by means of LDAP utlis.
There is another way.
Create AbstractSoapInterceptor with this body :
private final static QName SECURITY_QNAME = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security", "");
private static CertificateFactory certFactory;
public xxx() throws CertificateException {
super(Phase.PRE_PROTOCOL);
certFactory = CertificateFactory.getInstance("X.509");
}
#SneakyThrows
#Override
public void handleMessage(SoapMessage message) throws Fault {
SoapHeader header = (SoapHeader) message.getHeader(SECURITY_QNAME);
Node binarySignatureTag = ((Element) header.getObject()).getFirstChild();
BinarySecurity token = new X509Security((Element) binarySignatureTag, new BSPEnforcer());
InputStream in = new ByteArrayInputStream(token.getToken());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
}
Register it in configuration of endpoint :
#Bean
public Endpoint endpoint() throws CertificateException {
EndpointImpl endpoint = new EndpointImpl(springBus(), xxxPortType());
endpoint.setServiceName(xxxService().getServiceName());
endpoint.publish("/xxxx");
endpoint.getInInterceptors().add(new xxx());
return endpoint;
}
I have a fairly simple case where I am trying to add HTTP headers (not SOAP headers) to a request I am making using Spring's WebServiceTemplate.
I have defined a ClientInterceptor where I am doing:
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
try {
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection = (HttpComponentsConnection) context.getConnection();
connection.addRequestHeader("Authorization", String.format("Bearer %s", someAccessToken));
} catch (IOException exception) {
// Do nothing
}
return true;
}
This is how I configure my SomeClient which extends WebServiceConfigurationSupport:
#Bean
public SomeClient someClient() {
...
SomeClientImpl service = new SomeClientImpl();
service.setObjectFactory(new com.path.ObjectFactory());
service.setDefaultUri(someUri);
service.setMarshaller(marshaller);
service.setUnmarshaller(marshaller);
service.setxStreamMarshaller(xStreamMarshaller);
service.setInterceptors(new ClientInterceptor[]{wss4jSecurityInterceptor()});
service.setMessageSender(new HttpComponentsMessageSender());
service.setInterceptors(new ClientInterceptor[]{wss4jSecurityInterceptor(), addHttpHeaderInterceptor()});
return service;
}
#Bean
public ClientInterceptor addHttpHeaderInterceptor() {
return new AddHttpHeaderInterceptor(someAccessToken);
}
#Bean
public Wss4jSecurityInterceptor wss4jSecurityInterceptor() {
Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
interceptor.setSecurementActions(securementAction);
interceptor.setSecurementUsername(securementUsername);
interceptor.setSecurementPassword(securementPassword);
interceptor.setSecurementPasswordType(WSConstants.PW_TEXT);
interceptor.setSecurementMustUnderstand(false);
return interceptor;
}
But the Authorization header is not being added. I have also tried with a CustomMessageCallback:
public class CustomMessageCallback implements WebServiceMessageCallback {
private String headerKey;
private String headerValue;
public CustomMessageCallback(String headerKey, String headerValue) {
this.headerKey = headerKey;
this.headerValue = headerValue;
}
#Override
public void doWithMessage(WebServiceMessage webServiceMessage) throws IOException, TransformerException {
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection conn = (HttpComponentsConnection) context.getConnection();
HttpPost post = conn.getHttpPost();
post.addHeader(headerKey, headerValue);
}
}
But it does not seem to work as well. What am I doing wrong, why the Authorization header is not being added? Thanks!
Use the HeadersAwareSenderWebServiceConnection interface instead of the actual underlying connection.
TransportContext context = TransportContextHolder.getTransportContext();
HeadersAwareSenderWebServiceConnection connection = (HeadersAwareSenderWebServiceConnection) context.getConnection();
connection.addRequestHeader("Authorization", String.format("Bearer %s", "********"));
Now if you upgrade/switch HTTP library you don't need to change this code.
To answer your question about what you are doing wrong is that you are casting to the wrong class. Yes the class you are using is deprecated but it is part of the library you are using, you cannot just cast to a different class without changing the underlying HTTP library.
What I did in past is to use a WebServiceMessageCallback like this one:
public class WsHttpHeaderCallback implements WebServiceMessageCallback
{
private String headerKey;
private String headerValue;
private String soapAction;
public WsHttpHeaderCallback(String headerKey, String headerValue, String soapAction)
{
super();
this.headerKey = headerKey;
this.headerValue = headerValue;
this.soapAction = soapAction;
validateRequiredFields();
}
public WsHttpHeaderCallback()
{
super();
}
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException
{
validateRequiredFields();
addRequestHeader(headerKey, headerValue);
if (StringUtils.hasText(this.soapAction))
{
AxiomSoapMessage axiomMessage = (AxiomSoapMessage) message;
axiomMessage.setSoapAction(this.soapAction);
}
}
private void validateRequiredFields()
{
if( !StringUtils.hasText(headerKey) )
{
throw new IllegalArgumentException("Impossibile proseguire. Passato HEADER HTTP con chiave non valida: ["+headerKey+"]");
}
if( !StringUtils.hasText(headerValue) )
{
throw new IllegalArgumentException("Impossibile proseguire. Passato HEADER HTTP con valore non valido: ["+headerValue+"]");
}
}
private void addRequestHeader(String headerKey, String headerValue)
{
TransportContext context = TransportContextHolder.getTransportContext();
WebServiceConnection connection = context.getConnection();
if (connection instanceof HttpComponentsConnection)
{
HttpComponentsConnection conn = (HttpComponentsConnection) connection;
HttpPost post = conn.getHttpPost();
post.addHeader(headerKey, headerValue);
}
else if( connection instanceof ClientHttpRequestConnection )
{
ClientHttpRequestConnection conn = (ClientHttpRequestConnection)connection;
conn.getClientHttpRequest().getHeaders().add(headerKey, headerValue);
}
}
}
Then I used it in this way:
wsTemplate.marshalSendAndReceive(wsUrl, request, new WsHttpHeaderCallback(headerKey, headerValue, soapAction) );
In this way I successfully set all the needed HttpHeaders (in my case just one :) )
I hope it is usefull
Angelo
TL;DR
Your messageSender should be an instance of HttpComponentsMessageSender instead of HttpUrlConnectionMessageSender. Also you need to provide proper credentials.
getConnection() function of TransportContext returns an implementation of WebServiceConnection. Both HttpUrlConnection and HttpComponentsConnection are implementations of the same. So basically you are getting the wrong type of connection,hence the ClassCastException.
The ClientInterceptor will work for custom headers but not for Authorization header. For that, your HttpComponentsMessageSender needs to be configured with your credentials.
The proper configuration should be like this
#Value("${username}")
private String username;
#Value("${password}")
private String password;
#Bean
public SomeClient someClient() {
SomeClientImpl service = new SomeClientImpl();
service.setMessageSender();
//other configs
return service;
}
public HttpComponentsMessageSender getMessageSender(){
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setCredentials(getCredentials);
}
public UsernamePasswordCredentials getCredentials(){
return new UsernamePasswordCredentials(username, password);
}
I went through a similar exercise, for an endpointInterceptor the connection returns a HttpServletConnection. Therefore used the following and managed to get the HTTP headers added.
HttpServletConnection connection = (HttpServletConnection)context.getConnection();
HttpServletResponse response = connection.getHttpServletResponse();
HttpServletRequest request = connection.getHttpServletRequest();
response.addHeader("myheader", "myvalue");
Some additional tips:
If you want to send back the same header you received in the request, use following in the handleResponse method of the endpointInterceptor
response.addHeader("myheader", request.getHeader("myheader"));
If you are trying to add custom headers in an clientInterceptor to send to a downstream use below in the handleRequest method,
HttpUrlConnection connection = (HttpUrlConnection)context.getConnection();
connection.addRequestHeader("myheader", "myvalue");
I am using HP-ALM 12.01 which seems to be stock full of issues. I cannot update to another version at this time.
I am trying to get access to the rest api to upload test results automatically from JUnit. I am using the infrastructure shown here (example application -> Infrastructure). From which, my connection scripts passes base64 encoded login info to authentication-point/authenticate and I am retrieving a valid LWSSO cookie. However, when I use this cookie to connect to rest/site-session to receive my QCSession cookies, I am receiving a 411 Length Required error. I have attempted to hard code the Content-Length into the headers as shown here
public void GetQCSession(){
String qcsessionurl = con.buildUrl("rest/site-session");
Map<String, String> requestHeaders = new HashMap<String, String>();
requestHeaders.put("Content-Type", "application/xml");
requestHeaders.put("Accept", "application/xml");
requestHeaders.put("Content-Length", "0");
try {
Response resp = con.httpPost(qcsessionurl, null, requestHeaders);
con.updateCookies(resp);
System.out.println(resp.getStatusCode());
} catch (Exception e) {
e.printStackTrace();
}
}
This did not work. I have also tried modifying the infrastructure to automatically inject the Content-Length header, as shown here
private void prepareHttpRequest(
HttpURLConnection con,
Map<String, String> headers,
byte[] bytes,
String cookieString) throws IOException {
String contentType = null;
//attach cookie information if such exists
if ((cookieString != null) && !cookieString.isEmpty()) {
con.setRequestProperty("Cookie", cookieString);
}
//send data from headers
if (headers != null) {
//Skip the content-type header - should only be sent
//if you actually have any content to send. see below.
contentType = headers.remove("Content-Type");
Iterator<Entry<String, String>>
headersIterator = headers.entrySet().iterator();
while (headersIterator.hasNext()) {
Entry<String, String> header = headersIterator.next();
con.setRequestProperty(header.getKey(), header.getValue());
}
}
// If there's data to attach to the request, it's handled here.
// Note that if data exists, we take into account previously removed
// content-type.
if ((bytes != null) && (bytes.length > 0)) {
con.setDoOutput(true);
//warning: if you add content-type header then you MUST send
// information or receive error.
//so only do so if you're writing information...
if (contentType != null) {
con.setRequestProperty("Content-Type", contentType);
}
OutputStream out = con.getOutputStream();
out.write(bytes);
out.flush();
out.close();
con.setRequestProperty("Content-Length", Integer.toString(bytes.length));
} else {
con.setRequestProperty("Content-Length", "0");
}
}
which also does not work.
note that setRequestProperty simply does a .set(key, value) to a MessageHeader
Has anyone dealt with this issue before or know how to resolve it?
Note that none of these issues occurs with postman. All 4 cookies are generated after a site-session post.
The code Example from Barney was slightly expanded since it was not adapted for ALM 12.5 setting.
Main difference is, that there are more cookies and cookies are attached to header
Config config = new Config(dataService);
String almURL = "https://" + config.host() + "/qcbin";
client = ClientBuilder.newBuilder().build();
target = client.target(almURL).path("api/authentication/sign-in");
invocationBuilder = target
.request(new String[] {"application/xml"})
.accept(new String[] {"application/xml"});
invocationBuilder.header("Authorization", getEncodedAuthString(config.username(), config.password()));
res = invocationBuilder.post(null);
String qcsessioncookie = res.getCookies().get("QCSession").getValue();
String almusercookie = res.getCookies().get("ALM_USER").getValue();
String xsrftokencookie = res.getCookies().get("XSRF-TOKEN").getValue();
String lswoocookie = res.getCookies().get("LWSSO_COOKIE_KEY").getValue();
/* Get the test-Set Data defect */
String midPoint = "rest/domains/" + config.domain() + "/projects/" + config.project();
target = client.target(almURL).path(midPoint).path("test-sets/1");
invocationBuilder = target
.request(new String[] {"application/xml"})
.accept(new String[] {"application/xml"});
concatenatedHeaderCookieString = "QCSession=" + qcsessioncookie + ";" + "ALM_USER=" + ";" + almusercookie + ";" + "XSRF-TOKEN=" + xsrftokencookie + ";"
+ "LWSSO_COOKIE_KEY=" + lswoocookie;
invocationBuilder.header("Cookie", concatenatedHeaderCookieString);
res = invocationBuilder.get();
The issue is that Java's HttpURLConnection ignores certain properties when manually set. One of these is Content-Length. This is because it automatically sets it itself. However, if you're not sending any data it simply doesn't send it, which ALM is not accepting due its outdated http protocols, as it expects to receive a Content-Length of 0.
To work around this you have to tell java to allow restrticted headers. This is done by running
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
for more information, look here Why does Content-Length HTTP header field use a value other than the one given in Java code?
POM.xml Dependency
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
<version>2.0</version>
</dependency>
Java Code: Login, Get the first defect, Logout
import java.util.Base64;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class App {
private static final String almURL = "https://abc.hp.com/qcbin";
private static final String isAuthenticatedPath = "authentication-point/authenticate";
private static final String qcSiteSession = "rest/site-session";
private static final String logoutPath = "authentication-point/logout";
private static String lswoocookie;
private static String qcsessioncookie;
public static String strDomain = "domain";
public static String strProject = "project";
public static String strUserName = "username";
public static String strPassword = "password";
public static Client client;
public static WebTarget target;
public static Invocation.Builder invocationBuilder;
public static Response res;
private static String getEncodedAuthString() {
String auth = strUserName + ":" + strPassword;
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
String authHeader = "Basic " + new String(encodedAuth);
return authHeader;
}
public static void main(String args[]){
client = ClientBuilder.newBuilder().build();
/* Get LWSSO Cookie */
target = client.target(almURL).path(
isAuthenticatedPath);
invocationBuilder = target.request(new String[] { "application/xml" });
invocationBuilder.header("Authorization", getEncodedAuthString());
res = invocationBuilder.get();
lswoocookie = res.getCookies().get("LWSSO_COOKIE_KEY").getValue();
/* Get QCSession Cookie */
target = client.target(almURL).path(qcSiteSession);
invocationBuilder = target
.request();
invocationBuilder.cookie("LWSSO_COOKIE_KEY", lswoocookie);
res = invocationBuilder.post(null);
qcsessioncookie = res.getCookies().get("QCSession").getValue();
/* Get the first defect */
String midPoint = "rest/domains/" + strDomain + "/projects/" + strProject;
target = client.target(almURL).path(midPoint).path("defects/1");
invocationBuilder = target
.request(new String[] { "application/json" });
invocationBuilder.cookie("LWSSO_COOKIE_KEY", lswoocookie);
invocationBuilder.cookie("QCSession", qcsessioncookie);
res = invocationBuilder.get();
/* Logout */
target = client.target(almURL).path(logoutPath);
invocationBuilder = target
.request();
invocationBuilder.cookie("LWSSO_COOKIE_KEY", lswoocookie);
invocationBuilder.cookie("QCSession", qcsessioncookie);
res = invocationBuilder.post(null);
}
}
i have implemented rest webservices using Jersey, and whenever some exception occur on the server side, the client gets a generic HTTP 500 Internal Server Error, with no more info of the real exception. I found that people usually catch any exception on the server side, then throws a WebApplicationException, but even this way the client keeps getting the generic HTTP 500 Internal Server Error.
This is my webservice:
#PUT
#Produces(MediaType.APPLICATION_XML)
#Consumes(MediaType.APPLICATION_XML)
#Path("/transmitir")
public WrapperTransmissaoRetorno receber(WrapperTransmissao wrapperRecepcao) {
WrapperTransmissaoRetorno retorno = new WrapperTransmissaoRetorno();
retorno.setCodigoMaster(new Random().nextInt());
retorno.setDataRetorno(new Date());
if(true){
throw new WebApplicationException("Este pau eh bem graudo");
}
return retorno;
}
This is the code that calls the client:
try {
WsTransmissaoCliente client = new WsTransmissaoCliente();
WrapperTransmissao wrapperRecepcao = new WrapperTransmissao();
Transferencia transferencia = new Transferencia();
transferencia.setCodigoTabela(23);
transferencia.setCodigoTransferencia(56);
transferencia.setDataRetorno(new Date());
transferencia.setDataTransmissao(new Date(System.currentTimeMillis()+3000000));
transferencia.setNomeTabela("CUPOM");
transferencia.setTipoOperacao(TipoOperacao.UPDATE);
wrapperRecepcao.setTransferencia(transferencia);
Jumento jumento = new Jumento();
jumento.setIdade(24);
jumento.setNome("José");
wrapperRecepcao.setObjeto(jumento);
// Cabrito cabrito = new Cabrito();
// cabrito.setAltura(56);
// cabrito.setPeso(120.0);
// wrapperRecepcao.setObjeto(cabrito);
WrapperTransmissaoRetorno retorno = client.transmitir(wrapperRecepcao);
System.out.println("Retorno do WS: "+retorno);
} catch (Exception e) {
WebApplicationException exx = (WebApplicationException) e;
exx.printStackTrace();
}
How to avoid this and get the real exception? Or at least the message?
UPDATE
Here is the object i am sending as a response:
package br.atualy.integracaocheckout.wrappers;
import java.util.Date;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class WrapperTransmissaoRetorno {
private Date dataRetorno;
private Integer codigoMaster;
public Date getDataRetorno() {
return dataRetorno;
}
public void setDataRetorno(Date dataRetorno) {
this.dataRetorno = dataRetorno;
}
public Integer getCodigoMaster() {
return codigoMaster;
}
public void setCodigoMaster(Integer codigoMaster) {
this.codigoMaster = codigoMaster;
}
#Override
public String toString() {
return "WrapperRecepcaoRetorno{" + "dataRetorno=" + dataRetorno + ", codigoMaster=" + codigoMaster + '}';
}
}
UPDATE 2
And here is the client:
import br.atualy.integracaocheckout.wrappers.WrapperTransmissao;
import br.atualy.integracaocheckout.wrappers.WrapperTransmissaoRetorno;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.WebTarget;
public class WsTransmissaoCliente {
private final WebTarget webTarget;
private final Client client;
private static final String BASE_URI = "http://localhost:8080/IntegracaoCheckout/webresources";
public WsTransmissaoCliente() {
client = javax.ws.rs.client.ClientBuilder.newClient();
webTarget = client.target(BASE_URI).path("transmissao");
}
// public String receber() throws ClientErrorException {
// WebTarget resource = webTarget;
// resource = resource.path("receber");
// return resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML).get(String.class);
// }
public WrapperTransmissaoRetorno transmitir(WrapperTransmissao requestEntity) throws ClientErrorException {
return webTarget.path("transmitir")
.request(javax.ws.rs.core.MediaType.APPLICATION_XML)
.put(javax.ws.rs.client.Entity.entity(requestEntity, javax.ws.rs.core.MediaType.APPLICATION_XML), WrapperTransmissaoRetorno.class);
}
public void close() {
client.close();
}
}
If using jawax.ws.rs.core.Response object.
SERVER :: In case of exception/failure set it as :
// do stuff
// here e.getMessage() can be custom failure message too
response = Response.serverError().entity(e.getMessage()).build();
// return response object
return response;
CLIENT :: On the client side check following :
if(response != null && reponse.getStatus() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
String serverErrorMsg = response.readEntity(String.class);
throw new Exception(serverErrorMsg);
}
Generally it's better to declare your method as returning a Response object instead of a user-defined type, and set the data as the entity. Then if you want to indicate that an exception has happened, you can just pass that exception as the entity of the Response you are returning.
e.g.
#GET
#Path("/foo")
public Response getFoo() {
try {
// do stuff
return Response.ok(someData).build();
} catch (Exception e) {
return Response.serverError().entity(e).build();
}
}
You'll notice that this way you don't ever end up actually throwing an exception out of your method, but rather return an explicit 500 response with an exception as the entity. This way you can still throw exceptions out of your code, but they'll be handled nicely.
EDIT
I'm not sure what your client wrapper code is doing, but you can pass the expected response data type into your call with the normal REST client:
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://foo.com/foo");
String response = target.request().get(String.class);
or you can also pull it out of the Response using the readEntity() method:
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://foo.com/foo");
Response response = target.request().get();
String entity = response.readEntity(String.class);
It sounds like what you need to do is check the return code, and then parse the entity as a either a WrapperTransmissaoRetorno or a WebApplicationException depending on what code was returned:
Response response = client.transmitir(wrapperRecepcao);
if (response.getStatus() == Response.Status.OK.getStatusCode()) { // 200
WrapperTransmissaoRetorno retorno = response.readEntity(WrapperTransmissaoRetorno.class);
// do stuff
} else if (response.getStatus() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) { // 500
WebApplicationException e = response.readEntity(WebApplicationException.class);
// do stuff
} // etc for other response codes
Use response object in webapplication excemption. It should work.
From java docs:
WebApplicationException(String message)
Construct a new instance with a blank message and default HTTP status code of 500.
Its a blank message. I haven't tried it myself. I guess this is the problem.
https://jersey.java.net/apidocs/2.6/jersey/javax/ws/rs/WebApplicationException.html
Even after all the suggestions i could not manage to throw the exception to the client.
So what i did was to put a String property inside my returning class, so when an exception occurs on the server side, this String will contain the exception message and i can get it on the client.