WebClient class missing from Apache CXF? - java

I am current building a JAX-RS client using Apache CXF version 3.1.11. I have been looking at some simple examples online and it appears the WebClient class has gone missing.
See the example code below that I found online.
public static void main(String[] args) throws JsonParseException,
JsonMappingException, IOException {
WebClient client = WebClient
.create("http://localhost:8080/",
Collections.singletonList(new JacksonJsonProvider()))
.path("test").accept(MediaType.APPLICATION_JSON_TYPE);
Message message = client.get(Message.class);
System.out.println("Message recieved : " + message);
}
I cannot find the WebClient class anywhere in the code and im using the following maven dependencies.
cxf-rt-frontend-jaxws
cxf-rt-transports-http
cxf-rt-transports-http-jetty
Please could someone confirm if I am missing a dependency or if WebClient has been removed from version 3.1.11

If you're not sure about specific provider implementation, you can use classes which are standar parts of JAX-RS instead, which are duo Client and WebTarget. But for marshalling things, sure, you probably still need specific dependency configured, either manually or it's been already provided by the Apache CXF.
Client client = ClientBuilder.newBuilder().build();
WebTarget target = client
.target("http://localhost:8080/");
Response response = target.request().get();
Message message = client.readEntity(Message.class);
/*
// now.. process the message
for (Message message : message.get...) {.. }
*/
response.close(); // close connections.

You need to add cxf-rt-frontend-jaxrs instead of cxf-rt-frontend-jaxws

Related

Make asynchronous SOAP call in Spring WebFlux

I have a Reactive Spring Application using WebFlux with a REST API. Whenever a user calls my API, I need to make a call to a SOAP service which exposes a WSDL, perform some operation and return the result.
How do I combine this call to a SOAP service with the Reactive WebFlux framework?
The way I see it, I can do it 2 different ways:
Construct and send the SOAP message using WebFlux' WebClient.
Wrapping a synchronous call using WebServiceGatewaySupport in a Mono / Flux.
The first approach has my preference, but I don't know how to do that.
Similar questions have been asked here:
Reactive Spring WebClient - Making a SOAP call, which refers to this blog post (https://blog.godatadriven.com/jaxws-reactive-client). But I could not get that example to work.
Using wsdl2java in a Gradle plugin I can create a client interface with asynchronous methods, but I don't understand how to use this. When using the WebServiceGatewaySupport I don't use that generated interface or its methods at all. Instead, I call the generic marshalSendAndReceive method
public class MySoapClient extends WebServiceGatewaySupport {
public QueryResponse execute() {
Query query = new ObjectFactory().createQuery();
// Further create and set the domain object here from the wsdl2java generated classes
return (QueryResponse) getWebServiceTemplate().marshalSendAndReceive(query);
}
}
Can anyone share a complete example going from a WebFlux controller to making a SOAP call and returning asynchronously? I feel like I am missing something crucial.
I had the same aim but without having WSDL file. As an input I had endpoint and XSD file that defines request's scheme that I should to send. Here is my piece of code.
First let's define our SOPA WebClient bean (to avoid creating it each time when we want to make a call)
#Bean(name = "soapWebClient")
public WebClient soapWebClient(WebClient.Builder webClientBuilder) {
String endpoint = environment.getRequiredProperty(ENDPOINT);
log.info("Initializing SOAP Web Client ({}) bean...", endpoint);
return webClientBuilder.baseUrl(endpoint)
.defaultHeader(CONTENT_TYPE, "application/soap+xml")
//if you have any time limitation put them here
.clientConnector(getWebClientConnector(SOAP_WEBCLIENT_CONNECT_TIMEOUT_SECONDS, SOAP_WEBCLIENT_IO_TIMEOUT_SECONDS))
//if you have any request/response size limitation put them here as well
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs()
.maxInMemorySize(MAX_DATA_BUFFER_SIZE))
.build())
.build();
}
public static ReactorClientHttpConnector getWebClientConnector(int connectTimeoutSeconds, int ioTimeoutSeconds) {
TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutSeconds * 1000)
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(ioTimeoutSeconds))
.addHandlerLast(new WriteTimeoutHandler(ioTimeoutSeconds)));
return new ReactorClientHttpConnector(HttpClient.from(tcpClient));
}
And now you can use the client to make SOAP calls like this:
#Slf4j
#Component
public class SOAPClient {
private final WebClient soapWebClient;
public SOAPClient(#Qualifier("soapWebClient") WebClient soapWebClient) {
this.soapWebClient = soapWebClient;
}
public Mono<Tuple2<HttpStatus, String>> send(String soapXML) {
return Mono.just("Request:\n" + soapXML)
.doOnNext(log::info)
.flatMap(xml -> soapWebClient.post()
.bodyValue(soapXML)
.exchange()
.doOnNext(res -> log.info("response status code: [{}]", res.statusCode()))
.flatMap(res -> res.bodyToMono(String.class)
.doOnNext(body -> log.info("Response body:\n{}", body))
.map(b -> Tuples.of(res.statusCode(), b))
.defaultIfEmpty(Tuples.of(res.statusCode(), "There is no data in the response"))))
.onErrorResume(ConnectException.class, e -> Mono.just(Tuples.of(SERVICE_UNAVAILABLE, "Failed to connect to server"))
.doOnEach(logNext(t2 -> log.warn(t2.toString()))))
.onErrorResume(TimeoutException.class, e -> Mono.just(Tuples.of(GATEWAY_TIMEOUT, "There is no response from the server"))
.doOnEach(logNext(t2 -> log.warn(t2.toString()))));
}
}
An important thing to mention here is that your soapXML should be in the format that defined by SOAP protocol obviously. To be more specific the message at least should starts and ends with soap:Envelope tag and consist all other data inside. Also, pay attention what version of SOAP protocol you are about to use as it defines what tags are allowed to use within the envelop and what not. Mine was 1.1 and here is specification for it
https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383494
cheers
After lots of pain and trouble I found a decent solution to this problem. Since a wsdl file is provided, you should visit this site: : https://www.wsdl-analyzer.com
you can input a wsdl file and view all operations of the soap service. once you find the desired operation you want to call, click on it, and it will show an example request in xml. Some how, you have to generate this xml to make the request. There are many methods to do so, and some are more complicated than others. I found that manual serialization works well, and is honestly easier than using libraries.
say you have an operation request like this:
<s11:Envelope>
<s11:body>
<s11:operation>
<ns:username>username</ns:username>
<ns:password>password</ns:password>
</sll:operation>
</s11:body>
<s11:Envelope>
then you would generate by
public String gePayload(String username, String password) {
StringBuilder payload = new Stringbuilder();
payload.append("<s11:Envelope><s11:body><s11:operation>");
payload.append("<ns:username>");
payload.append(username);
payload.append("</ns:username>");
payload.append("<ns:password>");
payload.append(password);
payload.append("</ns:password>");
payload.append("</s11:operation></s11:body></s11:Envelope>");
return payload.toString()
}
then the web calls
public String callSoap(string payload) {
Webclient webclient = Webclient.builder()
// make sure the path is absolute
.baseUrl(yourEndPoint)
.build()
return WebClient.post()
.contentType(MediaType.TEXT_XML)
.bodyValue(payload)
.retrieve()
.bodyToMono(String.class)
.block();
}
it is important that you specify the content type is xml, and that the class returns a string. web flux cannot easily convert xml to user defined classes. so you do have to preform manual parsing. You can specify jaxb2xmlEncoders and jaxb2xmlDecoders to endcode/decode a specific class, but I found this to be to complicated. the payload has to match the request format generated by wsdl analyzer, and getting the encoders/decoders to match that format can be a task of its own. you can further research these encoders if you want, but this method will work.
I'm facing the same problem for a week and still can't find the best solution.
If you want to test the WebClient you just need to post a string with the SOAP Envelope request. Something like that:
String _request = "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">\n" +
"<soap:Body>\n" +
"<request>\n" +
"<Example>blabla</Example>\n" +
"</request>\n" +
"</soap:Body>\n" +
"</soap:Envelope>";
WebClient webClient = WebClient.builder().baseUrl("http://example-service").build();
Mono<String> stringMono = webClient.post()
.uri("/example-port")
.body(BodyInserters.fromObject(_request))
.retrieve()
.bodyToMono(String.class);
stringMono.subscribe(System.out::println);
The problem is that you need to figure out how to serialize the whole SOAP Envelope (request and response) to a string.
This is only an example - not a solution.

Camel CXF Response Message to caller - Duplicate WS-Security Parts

I have a Camel CxfEndpoint Service defined. The Reception of the messages works fine, but the Response/Acknowledgement Message I am producing has a problem. The WS-Security parts/actions in the message are left in and therefore in the response I have my own WS-Security Parts (Signature Timestamp) plus the WS-Security Parts from the caller/original message.
The Message Acknowledgement is not accepted from the original caller and I suspect that this is the problem (that I have their Signature wth BinarySecuritySessionToken and our own).
The Camel route is rather simple for trying to resolve the issue:
from("myEndpoint")
.transacted()
.process(new PreProcessor())
.to("mock:end")
I have defined the Camel CxfEndpoint in the route as:
CxfEndpoint cxfEndpoint = new CxfEndpoint();
cxfEndpoint.setAddress("http://0.0.0.0:8888/services/Service");
cxfEndpoint.setWsdlURL("Service.wsdl");
cxfEndpoint.setCamelContext(camelContext);
....
Problem example Timestamp:
<wsu:Timestamp wsu:Id="TS-6757512FE17DCDC903153191998160526">
<wsu:Created>2018-07-18T13:19:41.605Z</wsu:Created>
<wsu:Expires>2018-07-18T13:24:41.605Z</wsu:Expires>
</wsu:Timestamp>
<u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="uuid-b2a1c0b2-8263-4afc-bc99-f8a46da80ce7-693">
<u:Created>2018-07-18T13:19:42.905Z</u:Created>
<u:Expires>2018-07-18T13:24:42.905Z</u:Expires>
</u:Timestamp>
The general structure of the response message seems to be fine, but I need to strip the WS-Security Action Parts from the message.
Is there a way to strip these parts or do I need to construct a entirely new message?
Please let me know if you need additional information, thanks.
So I fixed it by adding another Interceptor for removing the Security Header.
I would like to know if this is a acceptable approach or if there is a better solution to this problem.
public class RemoveSecurityHeadersOutInterceptor extends AbstractSoapInterceptor
{
public RemoveSecurityHeadersOutInterceptor(String phase) {
super(Phase.PRE_PROTOCOL);
}
public void handleMessage(SoapMessage message) throws Fault
{
List<Header> headers = message.getHeaders();
headers.removeIf(h -> h.getName().getLocalPart().equals("Security"));
}
}

Java Wiremock - mock external server during testing

Suppose the application is dependent on a REST service on a external server, http://otherserver.com. For testing, I would like to simulate the external rest call (via Wiremock) within a JUnit environment. Starting a seperate server consumes time and is not easy. Working with WiremockRule looks the right direction. Creating simulation controllers is not an elegant way as Wiremock is available.
E.g. get( "http://otherserver.com/service3/");
PS: of course I know that I can simulate a REST call via Mockito.
Simulating localhost with Wiremock is easy. How can I use that code to simulate other servers and services? I copied parts from the popular Baeldung examples.
public class WireMockDemo {
#Rule
public WireMockRule wireMockRule = new WireMockRule();
#Test
public void wireMockTestJunitOtherServer() {
try {
// **this does not work...**
configureFor("otherserver.com", 8080);
stubFor(get(urlPathMatching("/service2/.*"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("\"testing-library\": \"WireMock\"")));
// Test via simple client
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://otherserver:8080/service2/test");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);
System.out.println( "Response = " + stringResponse);
// Test via JUnit
verify(getRequestedFor(urlEqualTo("/service2/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());
assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("\"testing-library\": \"WireMock\"", stringResponse);
} catch( Exception e) {
e.printStackTrace();
}
}
// Support methods
private String convertHttpResponseToString(HttpResponse httpResponse) throws IOException {
InputStream inputStream = httpResponse.getEntity().getContent();
return convertInputStreamToString(inputStream);
}
private String convertInputStreamToString(InputStream inputStream) {
Scanner scanner = new Scanner(inputStream, "UTF-8");
String string = scanner.useDelimiter("\\Z").next();
scanner.close();
return string;
}
}
Your application code should not have the http://otherserver.com hardcoded, it should be configurable. When running normally it should point to http://otherserver.com, when running in test mode it should be pointed to http://localhost:<port> where <port> is where you have started your Wiremock server (preferably dynamic to avoid port clashes)
TL; DR:
No, you cannot.
What WireMock does, is to establish a Jetty server simulating a remote server you need to send request to. However, it does not change your hosts file or DNS mapping and automatically "redirect" your real request for remote server to localhost. In tests you still need to send request to localhost.
What you can do, if you are using Spring Boot, is to create two application.yml file(or another properties file) in main and test package, with same structure of keys, but the value in src/main/resources/application.yml is the real URL you request(like http://example.com/api), and that in src/test/resources/application.yml you put localhost/api.
By the way, to clarify, MockMvc is not for simulation of external 3rd party server request that your application depends on, but requests sent to the endpoints of your application. In MockMvc tests, your application is who receives the request, but in WireMock tests, your applications sends request.
Some working example:
// configure static, class-level rule for all tests, like #BeforeClass.
// this launches a Jetty server with configurations
#ClassRule
public static WireMockClassRule classRule = new WireMockClassRule(options().
port(80).httpsPort(443));
// Divide #ClassRule and #Rule,
// to bypass JUnit limitation of "#Rule cannot be static"
#Rule
public WireMockClassRule rule = classRule;
#Test
public void shouldReturnGivenJson() throws Exception {
// stubFor() also works; givenThat() is more TDD-ish
givenThat(post(urlEqualTo("/service2/test")) // <----- note here: without host name
.willReturn(WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.withBody("{\"status\":\"up\"}")));
// .... your connection here
I suggest to begin with urlEqualTo(), without messing around with regex. Then you progress to urlMatching().
Also, use org.apache.http.util.EntityUtils to get content from the response. This is the official, built-in way to process the response. And, use a ResponseHandler because it will consume() the response without manually cleaning the resources.
Check HttpClient documentation for more details.

Interceptor for {}WebClient has thrown exception, unwinding now Could not send Message

I am trying to access sample Rest method using Webtarget code in Websphere Liberty profile deployed as war and getting following exception.
[WARNING ] Interceptor for {https://www.google.com}WebClient has thrown exception, unwinding now
Could not send Message.
Its working when directly run with java main method.
#GET
#Produces("text/plain")
#Path("/hello")
public Response healthCheck() {
ClientConfig configuration = new ClientConfig();
configuration = configuration.property(ClientProperties.CONNECT_TIMEOUT, 30000);
configuration = configuration.property(ClientProperties.READ_TIMEOUT, 30000);
configuration = configuration.property(ClientProperties.PROXY_URI, "http://xxx.xxx.com:8080");
configuration.connectorProvider(new ApacheConnectorProvider());
Client client = ClientBuilder.newClient(configuration);
WebTarget target = client.target(
"https://www.google.com");
String content = target.request().get(String.class);
System.out.println(content);
}
Any help is appreciated? Its simple task but taking lot of time.
The ClientConfig and ClientProperties types are specific to Jersey. While you might have them in your application, they will almost certain conflict with WebSphere's JAX-RS implementation based on CXF. If you post the full logs, I may be able to confirm that.
Try using the JAX-RS spec API types instead of the Jersey types - and use the IBM properties (unfortunately, these properties are not portable) like this:
#GET
#Produces("text/plain")
#Path("/hello")
public Response healthCheck() {
Client client = ClientBuilder.newBuilder()
.property("com.ibm.ws.jaxrs.client.connection.timeout", 30000)
.property("com.ibm.ws.jaxrs.client.receive.timeout", 30000)
.property("com.ibm.ws.jaxrs.client.proxy.host", "xxx.xxx.com")
.property("com.ibm.ws.jaxrs.client.proxy.port", "8080")
.build();
WebTarget target = client.target(
"https://www.google.com");
String content = target.request().get(String.class);
System.out.println(content);
return Response.ok(content).build();
}
Hope this helps, Andy

What is the best way to unit test REST Endpoints (Jersey)

I have a REST controller that has multiple GET/POST/PUT methods that all respond/request JSON.
I am not using Spring in this application (yet).
I was looking into the REST-assured framework and I like how that looks but I can only use it when my web server is up and running.
Is there a way for me to run a in-memory web server, or something like that?
Are there any examples of REST endpoint testing that someone can provide?
If you are using JAX-RS 2.0 you should find your answer here
You can take a look at the example also
An integration test example, could be:
public class CustomerRestServiceIT {
#Test
public void shouldCheckURIs() throws IOException {
URI uri = UriBuilder.fromUri("http://localhost/").port(8282).build();
// Create an HTTP server listening at port 8282
HttpServer server = HttpServer.create(new InetSocketAddress(uri.getPort()), 0);
// Create a handler wrapping the JAX-RS application
HttpHandler handler = RuntimeDelegate.getInstance().createEndpoint(new ApplicationConfig(), HttpHandler.class);
// Map JAX-RS handler to the server root
server.createContext(uri.getPath(), handler);
// Start the server
server.start();
Client client = ClientFactory.newClient();
// Valid URIs
assertEquals(200, client.target("http://localhost:8282/customer/agoncal").request().get().getStatus());
assertEquals(200, client.target("http://localhost:8282/customer/1234").request().get().getStatus());
assertEquals(200, client.target("http://localhost:8282/customer?zip=75012").request().get().getStatus());
assertEquals(200, client.target("http://localhost:8282/customer/search;firstname=John;surname=Smith").request().get().getStatus());
// Invalid URIs
assertEquals(404, client.target("http://localhost:8282/customer/AGONCAL").request().get().getStatus());
assertEquals(404, client.target("http://localhost:8282/customer/dummy/1234").request().get().getStatus());
// Stop HTTP server
server.stop(0);
}
}

Categories

Resources