Adding a custom http header to a spring boot WS call (wstemplate) - java

I followed this guide "Consuming a SOAP web service", at
https://spring.io/guides/gs/consuming-web-service/
and changed it to call my own internal SOAP service, it makes the call
as expected, however now I need to pass an http header via the WsTemplate,
what is the easiest way to do this?

public class WsHttpHeaderCallback implements WebServiceMessageCallback
{
public WsHttpHeaderCallback()
{
super();
}
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException
{
String headerKey="headerkey";
String headerValue="headervalue";
addRequestHeader(headerKey, headerValue);
}
private void addRequestHeader(String headerKey, String headerValue) throws IOException
{
TransportContext context = TransportContextHolder.getTransportContext();
WebServiceConnection connection = context.getConnection();
if (connection instanceof HttpUrlConnection) {
HttpUrlConnection conn = (HttpUrlConnection) connection;
conn.addRequestHeader(headerKey, headerValue);
}
}
}

I'm not sure if this helps but found some documentation
For setting WS-Addressing headers on the client, you can use the org.springframework.ws.soap.addressing.client.ActionCallback. ...
webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));

I've faced the same problem. If it can help someone, I've found a solution here: Spring WS Add Soap Header in Client
The idea is to create a class implementing org.springframework.ws.client.core.WebServiceMessageCallback and override the doWithMessage() method.
The doItMessage() method takes a WebServiceMessage as argument and is invoqued by the springWs process before sending the request, allowing to modify it before it is send.
What is done in the exemple above is marschalling the object and adding it to the header of the request.
In my case I have to be carefull with XML annotions of the object to be set as header, especially the #XmlRootElement with the namespace attribute.
Once this is done, the WSClient has to be adjusted to use the marshalSendAndReceive() method that takes a request and an uri, a payload object, and a WebServiceMessageCallback.

Related

CXF Ws-Addressing requestContext issue

I'm using java with CXF to create a client with WS-Addressing features:
String url = "http://example.com";
CxfService service = new CxfService(new WSAddressingFeature());
CxfServicePort port = service.getCxfServicePort();
AddressingProperties addressingProperties = new AddressingProperties();
RelatesToType relatesToType = new RelatesToType();
relatesToType.setRelationshipType("correlationid");
addressingProperties.setRelatesTo(relatesToType);
BindingProvider bp = (BindingProvider) port;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url);
bp.getRequestContext().put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES, addressingProperties);
I have an interceptor that sets the value of RelatesTo property:
public class CorrelationIdInterceptor extends AbstractSoapInterceptor {
public CorrelationIdInterceptor() {
super(Phase.POST_LOGICAL);
}
public void handleMessage(SoapMessage message) throws Fault {
AddressingProperties addressingProperties = (AddressingProperties) message.get(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES);
addressingProperties.getRelatesTo().setValue(UUID.randomUUID().toString());
}
The issue with above configuration is that all SOAP requests are sharing the same requestContext (as indicated on https://cxf.apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe?. That also means that every message has the same MessageID and I need to avoid that.
I could use the thread.local.request.context=true flag, but then I don't know where and how to put the creation of AddressingProperties object. Moreover, setting the flag to true also clears my endpoint address, which is a bit frustrating.
What is recommended way of making sure each SOAP request is using an unique AddressingProperties and MessageID ?
Kind regards

Using SOAPAction in HTTP header to consume SOAP Service in JAVA

I'm trying to implement a soap service consumer in Java, using spring WebServiceGatewaySupport.
When I'm using curl to consume the service as below, it is giving proper response.
curl -d #request.xml -H 'SOAPAction:abc:mnEvent#DoAction' https://myhost.org/cd/doAction.jsp
I'm trying to implement the same using JAVA, by adding following HttpHeaders in a template class inheriting from WebServiceGatewaySupport
public O callWebService(String url, I request) {
return (O) getWebServiceTemplate().marshalSendAndReceive(url, request, new WebServiceMessageCallback() {
#Override
public void doWithMessage(WebServiceMessage message) {
TransportContext transportContext = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection = (HttpComponentsConnection) transportContext.getConnection();
connection.getHttpPost().addHeader("SOAPAction", "abc:mnEvent#DoAction");
}
});
}
With this code, I'm getting an error message like below.
SOP-330006 The method 'DoAction, ""' is not defined in SOAP service 'abc:mnEvent'.
What do I miss here when moving curl command to JAVA?
The error message SOP-330006 The method 'DoAction, ""' is not defined in SOAP service 'abc:mnEvent'. indicates, there are two soap actions in the request.
Explicit SoapAction added in HttpHeader
Implicit SoapAction in SoapMessage
To avoid this issue, we need to remove the soapAction from header and set it in SoapMessage.
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
soapMessage.setSoapAction("abc:mnEvent#DoAction");

Zuul proxy - how to forward requests to services depending on request path

Problem
How to forward requests in Spring Cloud application? I need to forward requests to other services depending on the part of uri.
For example
HTTP GET http://user-application/api/users, returns users JSON.
HTTP GET http://user-application/api/proxy/jobs-application/api/jobs, returns jobs JSON, but this request should be forwarded to another application:
HTTP GET http://jobs-application/api/jobs.
Any HTTP method is allowed, not only GET.
Context
I have a SpringBoot Application, User application which has REST end-points which return data.
For example GET http://user-application/api/users would return users in the JSON format.
User application also has an HTTP end-point which should forward the request to other applications - let's call one of them Jobs application.
This end-point is HTTP {ANY_METHOD} /api/proxy/{dynamic-service}/{dynamic-path} as an example,
GET http://user-application/api/proxy/jobs-application/api/jobs
Please, note, initial request comes to the User application, while then it is forwarded to the Jobs application.
Approaches
I put some my approaches which I think about. Maybe you have done similar things in the past, so you could share your experience doing so. Or even improve one of my approaches.
ProxyController approach
I would create a ProxyController in User application with mapping /proxy
#Controller
#RequestMaping("/proxy/**")
ProxyController
public void proxy(final HttpServletRequest request, HttpResponse response) {
final String requestUri = request.getRequestUri();
if (!requestUri.startsWith("/api/proxy/")) {
return null; // Do not proxy
}
final int proxyIndex = "/api/proxy/".lenght(); // Can be made a constant
final String proxiedUrl = requestUri.subString(proxyIndex, requestUri.lenght());
final Optional<String> payload = retrievePayload(request);
final Headers headers = retrieveHeaders(request);
final HttpRequest proxyRequest = buildProxyRequest(request, headers);
payload.ifPresent(proxyRequest::setPayload);
final HttpResponse proxyResponse = httpClient.execute(proxyRequest)
pdateResponse(response, proxyResponse);
}
The problem with this approach, I have to write a lot of code t build a proxy request, to check if it has payload and if it has, copy it into proxy request, then copy headers, cookies etc to the proxy request, copy HTTP verb into proxy request. Then when I get proxy response, I have to populate its details into the response.
Zuul approach
I was inspired by ZuulFilters:
https://www.baeldung.com/spring-rest-with-zuul-proxy
https://stackoverflow.com/a/47856576/4587961
#Component
public class ProxyFilter extends ZuulFilter {
private static final String PROXY_PART = "/api/proxy";
private static final int PART_LENGTH = PROXY_PART.length();
#Autowired
public ProxyFilter() {
}
#Override
public boolean shouldFilter() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
return requestURI.startsWith(PROXY_PART);
}
#Override
public Object run() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
final String forwardUri = requestURI.substring(PART_LENGTH);
context.setRouteHost(buildUrl(forwardUri));
return null;
}
#Override
public String filterType() {
return "proxy";
}
#Override
public int filterOrder() {
return 0;
}
private String retrieveRequestUri(final RequestContext context) {
final HttpServletRequest request = context.getRequest();
return request.getRequestURI();
}
private URL buildUrl(final String uri) {
try {
return new URL(uri);
} catch (MalformedURLException e) {
throw new RuntimeException(String.format("Failed to forward request uri %s}.", uri), e);
}
}
}
This code allows me to forward requests with less effort. However, we also use client side load balancer Ribbon and circuit breaker Hystrix in Spring Cloud Zuul out of box. How to enable these features? Will they be enabled out of box in context.setRouteHost(forwardUrl);
I would like to add another approach, maybe it can also work.
Static application.yml file to configure Zuul proxy approach
This approach does not requre dynamic Zuul Filters.
application.yml
zuul:
routes:
user-application:
path: /api/users/**
serviceId: user-service
stripPrefix: false
sensitiveHeaders:
# I have to define all other services similarly.
jobs-application:
path: /api/proxy/jobs/**
serviceId: jobs-application
stripPrefix: true
sensitiveHeaders:
It will work only if I know all the services my clients need to call before I deploy the User application. What if a new application is added dynamically? Then I will have to update the configuration.

CRest / How to configure to accecpt only JSON responce

I am using CRest to deserialize a JSON stream on Android. My first steps looks very promising.
To get the JSON stream from the server and not the XML one I use the following construct:
(Accept: application/json)
#EndPoint("http://localhost:8080/myserver/rest")
#Param(name = "Accept", value = "application/json", dest = Destination.HEADER)
public interface VersionService {
#ConnectionTimeout(10000)
#Path("/version")
VersionTO getVersion();
}
This works but it's a bit annoying to copy the "Param thing" for every service.
Is there a better way to configure all Services at one place only to return JSON?
Well I'm afraid there isn't any simple way in the current version, but feel free to open a request on the issue tracker.
Cheers,
Laurent
I faced a similar situation where I used a custom HTTP client. In your case it could look like as follows:
DefaultHttpClient client = new DefaultHttpClient();
client.addRequestInterceptor(new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
request.addHeader("Accept", "application/json");
}
});
CRestBuilder builder = new CRestBuilder();
builder.expectsJson();
builder.setRestService(new HttpClientRestService(client));
Another option is to set default parameter for ClientPNames.DEFAULT_HEADERS of the custom HttpClient instance.
Details can be found on http://hc.apache.org/httpcomponents-client-ga/tutorial/html/httpagent.html.

Can I wrap all JAX-RS requests with custom pre-dispatch, post-dispatch and error-handler code?

I have a number of classes exposed as JAX-RS request "handlers", using javax.ws.rs.Path annotations. I want to add certain actions before every request and after each request. Also, I need to create a global application-wide exception handler, which will catch everything thrown by these handlers and protocol.
Is it possible to achieve this with standard JAX-RS without creating of a custom class inherited from com.sun.jersey.spi.container.servlet.ServletContainer (I'm using Jersey).
You can also use ExceptionMappers. This mechanism which catch the exception thrown by your service and convert it to the appropriate Response:
#Provider
public class PersistenceMapper implements ExceptionMapper<PersistenceException> {
#Override
public Response toResponse(PersistenceException arg0) {
if(arg0.getCause() instanceof InvalidDataException) {
return Response.status(Response.Status.BAD_REQUEST).build();
} else {
...
}
}
}
For more information see:
JAX-RS using exception mappers
You could create a proxy RESTful service and use this as the entry point to all your other RESTful services. This proxy can receive requests, do any pre-processing, call the RESTful service required, process the response and then return something to the caller.
I have a set up like this in a project I've been working on. The proxy performs functions like authentication, authorisation and audit logging. I can go into further details if you like.
Edit:
Here is an idea of how you might want to implement a proxy that supports GET requests;
#Path("/proxy")
public class Proxy
{
private Logger log = Logger.getLogger(Proxy.class);
#Context private UriInfo uriInfo;
#GET
#Path("/{webService}/{method}")
public Response doProxy(#Context HttpServletRequest req,
#PathParam("webService") String webService,
#PathParam("method") String method)
{
log.debug("log request details");
//implement this method to work out the URL of your end service
String url = constructURL(req, uriInfo, webService, method);
//Do any actions here before calling the end service
Client client = Client.create();
WebResource resource = client.resource(url);
try
{
ClientResponse response = resource.get(ClientResponse.class);
int status = response.getStatus();
String responseData = response.getEntity(String.class);
log.debug("log response details");
//Do any actions here after getting the response from the end service,
//but before you send the response back to the caller.
return Response.status(status).entity(responseData).build();
}
catch (Throwable t)
{
//Global exception handler here
//remember to return a Response of some kind.
}
}
You can use filters to read and modify all requests and responses.

Categories