I have a basic spring websocket application which currently sends basic data to subscribers.
Currently the system uses the SimpMessageSendingOperations class as the message handler.
If I call SimpMessageSendingOperations.convertAndSend(destination, object) then the object is converted and received by the subscribed clients.
I would like to be able to send a custom header to the clients.
I have tried using the SimpMessageSendingOperations.convertAndSend(destination, object, headers) method to do this. However the custom header is not included in the stomp message.
Debugging through the code it looks like StompHeaderAccessor.toStompHeaderMap() method calls
toNativeHeaderMap() which uses the native header and the original native header maps to build up the stomp headers.
Is there a way to get a custom header added to stomp message?
StompHeaderAccessor extends NativeMessageHeaderAccessor which seems to be where the non-stomp headers live, except they are all stored in a single header called nativeHeaders - which itself is a map.
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public GenericMessage<Greeting> greeting(HelloMessage message) throws Exception {
Map<String, List<String>> nativeHeaders = new HashMap<>();
nativeHeaders.put("hello", Collections.singletonList("world"));
Map<String,Object> headers = new HashMap<>();
headers.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, nativeHeaders);
return new GenericMessage<Greeting>(new Greeting("Hello, " + message.getName() + "!"), headers);
}
A simple interceptor server-side to wrap your custom headers to the nativeHeaders header should be enough to expose them client-side where they would be available as a map message.headers.nativeHeaders. Simmilarly, you could write a client-side interceptor to move the nativeHeaders into the regular headers - so before your client is aware of the message, all the expected headers are simply in the message.headers.
Related
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.
We want to have an 'auth' service, using spring security, which:
authenticates incoming request,
if successful, adds userid to http header
forwards request (with header) to another service
What is the easiest way to achieve this kind of forwarding using Spring?
For this use case, we can create a new Spring Boot application (majorly know as API Gateway) that enables Zuul proxy (EnableZuulProxy). Create a custom Zuul PRE_TYPE filter that parses all the incoming requests. Its objective would be to check if an incoming request contains token, then call Authentication Service, retrieve the caller's information and add the user's identifier in the custom header.
Once the incoming request is passed through all the filters, then use zuul.routes.* properties to forward a request to appropriate services.
To call remote service (in this case, Authentication Service) we can use Spring's RemoteTokenServices class. And, BearerTokenExtractor class for extracting the token from incoming requests. Here's the sample code to get you started:
#Override
public Object run() {
final RequestContext requestContext = RequestContext.getCurrentContext();
final HttpServletRequest request = removeCustomHeaders(requestContext.getRequest());
requestContext.setRequest(request);
final Authentication authentication = tokenExtractor.extract(request);
if (Objects.nonNull(authentication)) {
try {
final OAuth2Authentication oAuth2Authentication = tokenServices.loadAuthentication(authentication.getPrincipal().toString());
final Map<String, String> userAuthDetails = (Map<String, String>) oAuth2Authentication.getUserAuthentication().getDetails();
requestContext.addZuulRequestHeader(USERNAME_HEADER, oAuth2Authentication.getPrincipal().toString());
// Add other required information in headers.
}
catch(final AuthenticationException | RestClientException |OAuth2Exception e){
I'm currently writing a REST API that uses a CXF interceptor to add certain headers to each request.
The code of this interceptor is:
public class TestHeaderInterceptor extends AbstractOutDatabindingInterceptor {
public TestHeaderInterceptor() {
super(Phase.SEND);
}
#SuppressWarnings("unchecked")
#Override
public void handleMessage(Message message) {
MultivaluedMap<String, Object> headers = (MetadataMap<String, Object>) message.get(Message.PROTOCOL_HEADERS);
if (headers == null) {
headers = new MetadataMap<String, Object>();
}
headers.add("X-Test", "test");
message.put(Message.PROTOCOL_HEADERS, headers);
}
}
So, as you can see I'm adding a header called X-Test with value test. When I use a CXF REST client (proxy based), I'm using the following code to add the interceptor to the client:
Client client = WebClient.client(clientObj);
ClientConfiguration config = WebClient.getConfig(client);
List<Interceptor<? extends Message>> interceptors = new ArrayList<Interceptor<? extends Message>>();
interceptors.add(new TestHeaderInterceptor());
config.setOutInterceptors(interceptors);
My REST API only has 2 actions:
#GET
#Produces(JSON_UTF8)
#Path("test/{id}")
Test test(#PathParam("id") String id);
#POST
#Produces(JSON_UTF8)
#Path("test2/{type}")
#Consumes(JSON_UTF8)
Test test2(Test obj, #PathParam("type") Type type);
The test/{id} method works successfully and it adds the header (checked with Wireshark). However, the test2/{type} call does not add the header to the request.
The weirdest thing is that, while using debug, the interceptor code is clearly invoked, which leaves me to think that somehow Apache CXF is ommitting the headers I add.
That's also the reason why I'm using the Phase.SEND phase in stead of Phase.MARSHALL, just because I thought my headers are ommitted somewhere in the process of running through all these phases. But even now the headers are still missing.
After some debugging I found out that the CXF interceptor chain is different when I'm using the test2 call, for example:
Interceptor chain with test:
[2014-06-13 10:49:48,535] - [DEBUG] - [Default Executor-thread-1] - [PhaseInterceptorChain.java:682] - Chain org.apache.cxf.phase.PhaseInterceptorChain#1384d8d1 was modified. Current flow:
pre-logical [ClientRequestFilterInterceptor]
prepare-send [MessageSenderInterceptor]
marshal [TestHeaderInterceptor]
prepare-send-ending [MessageSenderEndingInterceptor]
Interceptor chain with test2:
[2014-06-13 10:50:02,205] - [DEBUG] - [Default Executor-thread-1] - [PhaseInterceptorChain.java:682] - Chain org.apache.cxf.phase.PhaseInterceptorChain#69a33de5 was modified. Current flow:
pre-logical [ClientRequestFilterInterceptor]
prepare-send [MessageSenderInterceptor]
write [BodyWriter]
marshal [TestHeaderInterceptor]
prepare-send-ending [MessageSenderEndingInterceptor]
As you can see there is an additional BodyWriter interceptor at the WRITE phase. I suppose that when writing the request body, you can no longer access the headers (because the body comes after the headers).
So, the fix was to actually move the TestHeaderInterceptor to a phase before the BodyWriter, so in my code I'm now using the following code in my constructor:
public TestHeaderInterceptor() {
super(Phase.SETUP);
}
I have try to sent payload that have type MultiValueMap to spring MVC controller via Outbound gateway. But it seem to be that map data not coming to the controller. Don't know what is wrong or missing. and my code is be like this:
Outbound config:
<int-http:outbound-gateway id="outGateway"
http-method="POST"
request-channel="responseChannel"
url="http://localhost:8081/SpringIntegration-In-out-test/responseReq"
extract-request-payload="true">
</int-http:outbound-gateway>
Controller Config:
#RequestMapping("/responseReq")
public ModelAndView goResponse(#RequestBody MultiValueMap<String,String> body){
Iterator it = body.entrySet().iterator();
while (it.hasNext()) {
MultiValueMap.Entry pairs = (MultiValueMap.Entry)it.next();
System.out.println(pairs.getKey() + " = " + pairs.getValue());
it.remove();
}
return new ModelAndView("response");
}
I use Iterator to get map value but it have nothing.
Eveything looks good.
I recommend you to 'sniff' your HTTP traffic and take a look what's going on with your body. On Windows I use TCP Trace. Here you should be sure that you really send a Map as payload.
From other side test your Controller separately, e.g. using some RestClient. My preference - Firefox plugin. Build here the form manually, which should be converted to the MultiValueMap in the DispatcherServlet and don't forget to present header Content-Type: application/x-www-form-urlencoded
If your outbound adapter is to be used in a unidirectional way (send-only), you should use outbound-channel-adapter :
<int-http:outbound-channel-adapter
id="outAdapter"
channel="responseChannel"
url="http://localhost:8081/SpringIntegration-In-out-test/responseReq"
http-method="POST"
charset="UTF-8"
/>
Secondly, make your request mapping more precise by defining RequestMethod as POST:
#RequestMapping(value="/responseReq", method=RequestMethod.POST)
Lastly, why you are converting your Spring Integration Message into JMS Message by setting extract-request-payload="true".
I know that v3.0 has method getHeader() but what about 2.3? Maybe it possible to get from steam?
UPDATE:
Actually, I need the HTTP response header RESTful application. In some reason I have decided to do this in servlet filter... but without success...
Solution #javax.ws.rs.core.Context HttpHeaders requestHeaders.
For example,
#javax.ws.rs.GET
public String invoceRestMethod(#Context HttpHeaders requestHeaders){
MultivaluedMap<String, String> map = headers.getRequestHeaders();
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
// processing header....
}
}
Maybe in will help for someone. But any case, for Servlet issue still is opened
You cannot get the header from the stream*.
What you have to do is insert a proxy response object into the filter chain before your Servlet is called, and have that capture the header.
* Actually, you could potentially capture stuff from the stream using a proxy response and decode the headers. But if you are inserting a proxy response, it is simpler to capture the headers directly.