'Cannot Add Http Headers to Message with Spring's WebServiceTemplate
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!
Solution 1:[1]
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.
Solution 2:[2]
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
Solution 3:[3]
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);
}
Solution 4:[4]
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 theendpointInterceptor
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 thehandleRequest
method,
HttpUrlConnection connection = (HttpUrlConnection)context.getConnection();
connection.addRequestHeader("myheader", "myvalue");
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Angelo Immediata |
Solution 3 | |
Solution 4 | maheeka |