CXF Ws-Addressing requestContext issue - java

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

Related

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

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.

SOAP Service Client doesn't send Mime headers for Basic Auth

Some background: This is a Weblogic Web Services created Service client creates via Eclipse. I believe this uses clientgen behind the scenes.
I'm trying to make a SOAP call that requires preemptive Basic Authentication. The request is being sent but the Mimeheaders I'm setting are not going with it. The recipient of the call has informed me that the request itself is coming through but any mimeheaders I set are not.
The service call is rather simple.
DescriptionService service = new DescriptionService(wsdlLocation, new QName("urn:descriptionService.service.company.com", "DescriptionService"));
service.setHandlerResolver(new HandlerResolver() {
#SuppressWarnings("rawtypes")
#Override
public List<Handler> getHandlerChain(final PortInfo portInfo) {
final List<Handler> handlerList = new ArrayList<Handler>();
handlerList.add(new SOAPDescriptionServiceHeaderHandler());
return handlerList;
}
});
DescriptionServicePortType portType = service.getDescriptionServicePort();
DescriptionRequest request = new DescriptionRequest();
request.setId(id);
DescriptionResponse description = portType.describe(request);
The handler is where I set the Mimeheaders:
#Override
public boolean handleMessage(final SOAPMessageContext context) {
final Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
final SOAPMessage message = context.getMessage();
if (outboundProperty.booleanValue()) {
try {
MimeHeaders mimeheaders = message.getMimeHeaders();
String encodedAuth = Base64.encode(new String("un:pw").getBytes());
mimeheaders.addHeader("Authorization", "Basic " + encodedAuth);
this.logMessage(message, outboundProperty.booleanValue(), false);
} catch (final Exception e) {
// Log Error
}
} else {
this.logMessage(message, outboundProperty.booleanValue(), false);
}
return true;
}
It does hit this handler and set the mimeheaders. If I set a break point and look at the mime headers before it leaves the handleMessage method, I can see that they are set.
I'm able to call the request and get a response in SoapUI. I set up preemptive basic auth and it works fine. When I send the request through the Java Client, I get no response and actually get an error that says it's the incorrect content type. I believe this error is referring to the fault response as I don't actually get the response (doesn't hit the handleMessage() method in the handler either) and I know the request is going through with text/xml which is what the error is asking for.
I'm unsure if it has something to do with the "preemptive" requirement? Is there a way to set basic auth set up this way as preemptive?
Thoughts?
Any assistance would be appreciated.
Basic Auth is done at the HTTP layer, not the SOAP layer, so you need to configure the underlying HTTP library. (MIME headers have nothing to do with it)
For example for CXF, have a look at this question HTTP basic authentication through CXF interceptor not working

Akka Camel websocket client producer

I am trying to communicate with a websocket server using Apache Camel AHC-Websocket Component with Akka Camel in Java. In this case, websocket endpoint is goint to be a well known websocket public service.
Using:
JDK 8.x
Akka Java API with Akka Camel, Akka Actor and Akka SLF4J 2.3.9
Apache Camel with AHC WS Component 2.14.1
I followed Akka Camel tutorial for Java located over here.
Short description: Every response received by UntypedProducerActor when I make a request returns CamelMessage with body field as null. But when I make request via ProducerTemplate I receive correct response.
Long description: I am getting strange behaviour from Akka Camel when I make a request and expect a response from the websocket endpoint. When I make a request to the endpoint via defined ActorRef, for example like so:
ActorRef wsProducer = getContext().actorOf(SimpleProducer.props("ahc-ws:echo.websocket.org"));
final Timeout timeout = new Timeout(3, TimeUnit.MINUTES);
final Future<Object> future = Patterns.ask(wsProducer, "Please, respond!", timeout);
final Object result = Await.result(future, timeout.duration());
I clearly see in the logs that Apache Camel websocket endpoint received a response:
DEBUG o.a.c.component.ahc.ws.WsEndpoint - received message --> Please, respond!
But the result object will be a CamelMessage with body field always set to null. The same CamelMessage present in public Object onTransformResponse(Object message) method of my SimpleProducer.
However, when I make request via ProducerTemplate like this:
final Camel camel = CamelExtension.get(getContext().system());
final CamelContext context = camel.context();
final ProducerTemplate template = camel.template();
Object result = template.requestBody("ahc-ws:echo.websocket.org", "Alpha is there!");
It works and result will contain correct response body: "Alpha is there!".
My SimpleProducer is pretty much the same as in tutorial:
public class SimpleProducer extends UntypedProducerActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private final String mEndpointUri;
public SimpleProducer(final String serverUrl) {
mEndpointUri = serverUrl;
}
#Override
public String getEndpointUri() {
return mEndpointUri;
}
#Override
public Object onTransformResponse(Object message) {
return super.onTransformOutgoingMessage(message);
}
#Override
public boolean isOneway() {
return false;
}
public static Props props(final String endpointUri) {
return Props.create(new Creator<SimpleProducer>() {
private static final long serialVersionUID = 1L;
#Override
public SimpleProducer create() throws Exception {
return new SimpleProducer(endpointUri);
}
});
}
}
Maybe somebody had the same issue and can help me out?

Solrj Authentication Fails On Write Operation

Just password protected solr on Jetty server. I am able to read/access data from solr using solrj :->
HttpSolrServer solr = new HttpSolrServer("http://111.111.111:8983/solr/mysolr");
HttpClientUtil.setBasicAuth((DefaultHttpClient) solr.getHttpClient(), "admin", "akshaybhatt");
but it gives me I/O Exception as below. There are other examples on SO about authentication but I have no idea how do I use authentication in Solrj. The below error comes only when I try to update a record (and possibly add a record, not tested yet)
org.apache.solr.client.solrj.SolrServerException: IOException occured when talking to server at: http://111.111.111.138:8983/solr/mysolrcore
at org.apache.solr.client.solrj.impl.HttpSolrServer.request(HttpSolrServer.java:507)
at org.apache.solr.client.solrj.impl.HttpSolrServer.request(HttpSolrServer.java:199)
at org.apache.solr.client.solrj.request.AbstractUpdateRequest.process(AbstractUpdateRequest.java:118)
at org.apache.solr.client.solrj.SolrServer.add(SolrServer.java:116)
at org.apache.solr.client.solrj.SolrServer.add(SolrServer.java:102)
at UpdateSolrTesting.AddToSolr(UpdateSolrTesting.java:228)
at UpdateSolrTesting.performaction(UpdateSolrTesting.java:141)
at UpdateSolrTesting.main(UpdateSolrTesting.java:101)
Caused by: org.apache.http.client.ClientProtocolException
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:867)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:57)
at org.apache.solr.client.solrj.impl.HttpSolrServer.request(HttpSolrServer.java:395)
... 7 more
Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:660)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:486)
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:863)
... 11 more
It is happening only because authentication parameter is not being sent while doing POST or DELETE calls, so so solution is you need to fix that in your http client
I am using solr 6.2.0 and its corresponding java client
So i created a new SolrHttp client which looks like below
public class TEHttpSolrClient extends HttpSolrClient {
private static final String UTF_8 = StandardCharsets.UTF_8.name();
public TEHttpSolrClient(String baseURL) {
super(baseURL);
}
#Override
public NamedList<Object> request(final SolrRequest request, String collection) throws SolrServerException, IOException {
ResponseParser responseParser = request.getResponseParser();
if (responseParser == null) {
responseParser = parser;
}
return request(request, responseParser, collection);
}
public NamedList<Object> request(final SolrRequest request, final ResponseParser processor, String collection)
throws SolrServerException, IOException {
HttpRequestBase method = createMethod(request, collection);
String userPass = "<username>:<password>";
String encoded = Base64.byteArrayToBase64(userPass.getBytes(UTF_8));
// below line will make sure that it sends authorization token every time in all your requests
method.setHeader(new BasicHeader("Authorization", "Basic " + encoded));
return executeMethod(method, processor);
}
}
Also to call the client you should call it like below
private static SolrClient solr = new TEHttpSolrClient.Builder("<solr core url>").build();
What you need is called "preemptive authentication". This tells the http client to authenticate on the first call to the url. The default behaviour is to send two request, when basic authentication is used. This might fail, as in your case, when the entity in the http call is not reusable.
Luckyly solr allready has a build in way to enable preemptive authentication by using a different client building factory for SolrHttpClientBuilder.
String userName = "someUserName";
String password = "secretPassword";
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(HttpClientUtil.PROP_BASIC_AUTH_USER, userName);
params.set(HttpClientUtil.PROP_BASIC_AUTH_PASS, password);
// set the params for authentication here
PreemptiveBasicAuthClientBuilderFactory.setDefaultSolrParams(params);
PreemptiveBasicAuthClientBuilderFactory preemptiveBasicAuthClientBuilderFactory = new PreemptiveBasicAuthClientBuilderFactory();
// create a new client builder from the preemptive client builder factory
SolrHttpClientBuilder httpClientBuilder = preemptiveBasicAuthClientBuilderFactory
.getHttpClientBuilder(Optional.empty());
// set the client builder to be used by the clientUtil
HttpClientUtil.setHttpClientBuilder(httpClientBuilder);
// the params need to be passed here too
CloseableHttpClient httpAuthClient = HttpClientUtil.createClient(params);
// now build the solr client with the special http client
Builder solrClientBuilder = new HttpSolrClient.Builder(solrClientConfig.getSolrUrl()).withHttpClient(httpAuthClient);
// create solr client
SolrClient client = solrClientBuilder.build();
Remember not to set the authorization params at the request level, otherwise the preemptive auth won't work.
Similar question asked already, you can look at the below links to get some idea.
Solr - instantiate HttpSolrServer with Httpclient
Solr Change CommonsHttpSolrServer To HttpSolrServer

wsContext.getMessageContext().get(MessageContext.SERVLET_REQUEST) returns null on Jetty?

I have an extension of this question. I have that exact code running on a Jetty Server, and other SOAP web services work perfectly. However, on this line:
HttpServletRequest req = (HttpServletRequest)mc.get(MessageContext.SERVLET_REQUEST);
System.out.println("Client IP = " + req.getRemoteAddr());
The server crashes with a null pointer exception. mc.get(MessageContext.SERVLET_REQUEST) is returning null.
By comparison, mc.get(MessageContext.HTTP_REQUEST_METHOD) returns "POST", so I assume that's working.
What can I do to fix this?
EDIT:
I've tried this fix to no avail.
I've also tried using the #Context annotation instead and got the same issue.
A System.out.println(mc) yields this:
{javax.xml.ws.wsdl.port={http://my.test.namespace.com/}testWSDLPort,
javax.xml.ws.soap.http.soapaction.uri="",
com.sun.xml.internal.ws.server.OneWayOperation=null,
javax.xml.ws.http.request.pathinfo=null,
...
...
and so on, and the list of values does NOT include javax.xml.ws.servlet.request, which is the value of MessageContext.SERVLET_REQUEST. What do I need to do to make sure the MessageContext has this value?
Currently the Jetty HTTP SPI JAX-WS implementation doesn't appear to properly inject the MessageContext into a web service. Try switching to Apache CXF instead. Once you have
cxf-2.6.2.jar
neethi-3.0.2.jar
xmlschema-core-2.0.3.jar
on your project build path, you have to create a servlet class that extends the CXFNonSpringServlet and overrides the loadBus function like so:
public class SOAPServlet extends CXFNonSpringServlet {
private static final long serialVersionUID = 1L;
private Map<String, Object> endpoints;
public SOAPServlet(){
endpoints = new HashMap<String, Object>();
}
#Override
public void loadBus(ServletConfig servletConfig) {
super.loadBus(servletConfig);
// You could add the endpoint publish codes here
Bus bus = getBus();
BusFactory.setDefaultBus(bus);
Set s = endpoints.entrySet();
Iterator p = s.iterator();
while(p.hasNext()){
Map.Entry m = (Map.Entry)p.next();
String address = (String)m.getKey();
Object impl = (Object)m.getValue();
System.out.println("Publishing " + address);
Endpoint.publish(address, impl);
}
}
public void publish(String address, Object impl){
endpoints.put(address, impl);
}
}
And then where you are configuring your server, add these lines:
Server server = new Server(8080);
// Configure SOAP servlet
SOAPServlet servlet = new SOAPServlet();
ServletHolder SOAPServletHolder = new ServletHolder(servlet);
ServletContextHandler SOAPContext = new ServletContextHandler(server,"/",ServletContextHandler.SESSIONS);
SOAPContext.addServlet(SOAPServletHolder, "/*");
// Set server context handlers
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(new Handler []{SOAPContext});
server.setHandler(contexts);
// Publish SOAP Web service endpoints
servlet.publish("/MyWebServiceRelativeURL", new MyWebServiceImpl());
server.start();
server.join();
I encountered the same issue - instead of retrieving the SERVLET_REQUEST like that and getting null, I used the following:
com.sun.net.httpserver.HttpExchange server = (com.sun.net.httpserver.HttpExchange) mc.get("com.sun.xml.internal.ws.http.exchange");
System.out.println("Client IP = " + server.getRemoteAddress().toString());
This allows for retrieving of the IP address, port, etc.

Categories