How to pass JSON on a GET URL with Spring Webclient - spring

I need to call an external service with URL that looks like this...
GET https://api.staging.xxxx.com/locations?where={"account":"bob"}
This is not my service and I have no influence over it, and my codebase at the moment is using Spring WebClient.
WebClient.create("https://api.staging.xxxx.com/")
.get()
.uri(uriBuilder -> uriBuilder.path("locations?where={'account':'bob'}").build())
Since WebClient sees the { bracket it tries to inject a value into the URL, which I don't want.
Can anyone suggest how I can do with with Spring WebClient?
Otherwise I will revert to OKHttp or another basic client to sent this request.

You can use UriUtils#encodeQueryParams to encode the JSON param:
String whereParam = "{\"account\":\"bob\"}";
//...
uriBuilder
.path("locations")
.queryParam("where", UriUtils.encodeQueryParam(whereParam, StandardCharsets.UTF_8))
.build()

One way to achieve this is to manually url encode the values before passing them to the uri builder.
Also I think that you should use queryParam() to specify query parameters.
uriBuilder ->
uriBuilder.path("locations")
.queryParam("where", "%7B'account':%20'bob'%7D")
.build()
Another option would be to configure the encoding mode when creating the WebClient
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
WebClient.builder().uriBuilderFactory(factory).build();

Related

Dynamic metric tagging with Spring WebClient

Trying to migrate some code from a RestTemplate to Webclient. The old code was wrapping a timer for metric collection around the RestTemplate call, and was adding two additional custom tags that are based on the request input.
We're creating a WebClient and adding the MetricsWebCLientFilterFunction to the builder as a filter. I've also seen the DefaultWebClientExchangeTagsProvider. The issue is that the only mechanism I've seen to apply tags is on that builder. We don't want to create a new WebClient on every request from the builder, and I'm not seeing a way to add tags to an individual webclient get/options/post.
Is there a mechanism to add tagging to individual metrics on the actual webclient get or are we limited to creating a builder every single time?
For context, we're trying to log client id and another custom identifier that isn't part of the URL pattern for the call.
The only idea I've had so far was adding an HTTP header and then creating a custom WebClientExchangeTagsProvider that would add the tag as the request comes in. The problem is that we don't want these headers going out to the external vendor services we're calling.
We're using Spring Boot 2.5.4 and it's a spring MVC app we'd like to migrate to webflux eventually.
There isn't a mechanism to post custom tags per request once a WebClient is created. However, one can do a pattern like this
val metricFilter = MetricWebClientFilterFunction(meterRegistry), CustomTagProvider(customValString), "metric-name", AutoTimer.ENABLED)
webClient.mutate().filter(metricFilter).build().get() ...
Then create a custom metric class
class CustomTagProvider(private val customValString: String) : DefaultWebClientExchangeTagProvider() {
override fun tags(request: ClientRequest, response: ClientRespose, throwable: Throwable): Iterable<Tag> {
val custom: Tag.of("custom", customValString)
val tags: MutableList<Tag> = mutableListOf(custom)
tags.addAll(super.tags(request, response, throwable))
return tags
}
}

Disable RestTemplate escaping

An existing API I have to call accepts a query parameter in the form
?key=001|ABC|123456
However RestTemplate, using its internal UriBuilder is escaping the | character to %7C.
Thus the parameter results in
?key=001%7CABC%7C123456
I've tried setting a custom UriTemplateHandler via setUriTemplateHandler, using a new DefaultUriBuilderFactory
final var uriBuilder = new DefaultUriBuilderFactory();
and disabling the encoding and parsing
uriBuilder.setEncodingMode(EncodingMode.NONE);
uriBuilder.setParsePath(false);
however another exception is thrown internally when constructing the URI
URI.create(url)
saying an illegal character is present, which is, obviously |.
How can I totally disable this behavior in Spring, using RestTemplate?
I cannot use escaped values.
Executing the same call via SoapUI, using a non-escaped URL, returns the correct response.
It seems that there is no clean way to do this with Spring classes.
In the end I've choosen for the raw HttpURLConnection, which, while not as easy-to-use, provide all the required functionality anyway.
The thing is internally, the DefaultUriBuilderFactory which is the one used to build the final URI, uses the java.net.URI class, which obey RFCs rules.
private URI createUri(UriComponents uric) {
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
uric = uric.encode();
}
return URI.create(uric.toString());
}
As you can see, even if you disable encoding via
uriBuilder.setEncodingMode(EncodingMode.NONE);
the final result is always a call to URI.create.
You cannot escape that, if not by providing a custom UriBuilderFactory.

Spring Integration DSL: How to add the HTTP.outboundGateway header?

What is the easiest way to add the HTTP.outboundGateway header in my program?
What I want to do is:
I first do the HTTP GET for the URL
http://localhost:8050/session
then I get the JSON
{
"session": "session8050"
}
I extract the value of the session variable and add that to the next HTTP GETas the session header variable.
Currently I have working code, but I was thinking could I do this easier? My implementation
Extracts the session variable from the JSON with the jsonPath method
Then the implementation adds the session variable to the integration flow message header with the enrichHeaders method
Then the implementation adds the session variable to the HTTP call header with the HeaderMapper class
My implementation is
integrationFlowBuilder
.transform(p -> authenticationJson)
.enrichHeaders(h -> h.header("Content-Type", "application/json"))
.handle(Http.outboundGateway("http://localhost:8050/session").httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.enrichHeaders(
h -> h.headerExpression("session", "#jsonPath(payload, '$.session')", true)
.handle(Http
.outboundGateway(completeFromUrl)
.httpMethod(HttpMethod.GET).mappedRequestHeaders("session").headerMapper(headerMapper())
.expectedResponseType(String.class))
My headerMapper is
#Bean
HeaderMapper headerMapper() {
final DefaultHttpHeaderMapper headerMapper = new DefaultHttpHeaderMapper();
final String[] headerNames = { "session" };
headerMapper.setOutboundHeaderNames(headerNames);
headerMapper.setUserDefinedHeaderPrefix("");
return headerMapper;
}
Is it possible to extract the session variable from the JSON and add it straight to the HTTP headers??
Why the HeaderMapper must be used? Why the integration flow message headers don't go straight to the HTTP.outboundGateway call as the payload goes?
First of all you need to understand that main goal of Spring Integration as any other EIP solution is to make components in the flow as isolated as possible, so in the future you can add some intermediate steps or remove without big impact for the whole solution and other components in your integration flow. This should be an answer to your questions about why HeaderMapper must be used.
As you see the contract of the HeaderMapper to remap MessageHeaders to the target protocol headers representation. There is nothing about payload, hence you need to map the value from the payload into the headers, first of all. And then say Http.outboundGateway() what should be remapped from the MessageHeaders into the HttpHeaders.
By default the DefaultHttpHeaderMapper (it is present there in the Http.outboundGateway()) maps only standard HTTP headers suitable for the HTTP request.
If you need to include some custom header, like in your case with that session, you really can use a custom configuration for the DefaultHttpHeaderMapper, or just configure a convenient option on the Http.outboundGateway():
.mappedRequestHeaders("session")
The setUserDefinedHeaderPrefix("") is not necessary from version 5.0. It is empty string by default now, since there is no requirements in the prefixes for custom headers in the HTTP protocol.

Spring MVC for Rest: How set a Locale value

I am working with Spring Framework 4.3.2
About Rest for POST and PUT the following situation happens.
For POST I have the following:
#PostMapping(consumes={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE})
public ResponseEntity<Void> saveOne(#Validated #RequestBody Persona persona){
Observe the method has declared #Validated.
I need do the validation and return the error text message according the Locale sent it by the client/user.
I can do it through Spring MVC Test, for that I have the following:
resultActions = mockMvc.perform(post(uri).contentType(MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_XML)
.locale(locale)
.content(...content...))).andDo(print());
Observe the .locale(locale) part.
Until here about JUnit through #Parameters I am able to send a list of Locales and thus see the error message in different languages. Until here all go how is expected
Now, the other way to access a Rest controller is through RestTemplate
I have the following:
RequestEntity<Persona> requestEntity = RequestEntity.post(uri)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.body(personaValid);
....
ResponseEntity<GenericCollection<ValidatedFieldErrorReport>> responseEntity_
= restTemplate.exchange(requestEntity, parameterizedTypeReference);
Sadly RequestEntity through the post does not support .locale(locale)
Even If I add .header("Accept-Language", locale.toString()) it does not work, same consideration for .header("locale", locale.toString()).
Note: I can confirm when I print the RequestEntity object it sends the expected Locale but in the server: it ignores the 'locale' sent it (how if it never has been not sent it from the beginning) and uses the default Locale available in the server. I am confused with this approach.
I want keep the RequestEntity and ResponseEntity objects. Their API are fluent.
Therefore what is the correct way?. I am with the impression an extra configuration in either client or server is need it in some place.
For RequestEntity you should use:
.header(HttpHeaders.ACCEPT_LANGUAGE, locale.toLanguageTag());

How to specify content type produced by a handler in Spring Messaging?

I'm experimenting with Spring 4 WebSocket STOMP application. Is there a way to explicitly specify content type of the returned message produced by a handler? By default the handler below produces application/json and processed by corresponding message converter.
#Controller
public class ProductController {
#MessageMapping("/products/{id}")
public String getProduct(#DestinationVariable int id) {
return getProductById(id);
}
}
I'm looking for something like #RequestMapping(produces = "text/xml") in Spring MVC.
UPDATE (reply to Rossen's answer):
Ideally I would like to be able to return both formats depending on what the user asks for. But if I have to choose, I would say XML and almost never JSON (XML is just an example, we use binary format). I went the second way you suggested - configuring custom converters instead of the default ones.
I have implemented custom MessageConverter extending AbstractMessageConverter. In the constructor I've registered appropriate supported MimeType.
Then I've registered my custom converter by overriding WebSocketMessageBrokerConfigurer's configureMessageConverters, and return false from the method to not add default converters.
As soon as my controller returns the value I get NPE in SendToMethodReturnValueHandler's postProcessMessage. This happens because CompositeMessageConverter contains only a single converter - my custom one. But my converter fails AbstractMessageConverter's supportsMimeType check and AbstractMessageConverter's toMessage returns null. This null causes the exception in postProcessMessage.
As the workaround I can register additional default MimeType application/json for my custom converter. But it looks like too dirty to me.
There is nothing like the produces condition. The only way to do this right now is to inject SimpMessagingTemplate and then use the convertAndSend method variation that takes a MessagePostProcessor. It could be made simpler. What's your use case? Are you using JSON primarily and need to use XML in a few places? Or do you need to use XML primarily? If it is the latter you can simply configure the converters to use instead of the default ones.
I see that you can change the headers changing the Message converter.
For example:
SockJsClient sockJsClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
..
stompClient.setMessageConverter(new StringMessageConverter());
or
stompClient.setMessageConverter(new SimpleMessageConverter());
when you use StringMessageConverter then the Java client add an content-type header.
GenericMessage [payload=byte[33], headers={simpMessageType=MESSAGE, stompCommand=SEND, nativeHeaders={destination=[/app/chatchannel], content-type=[text/plain;charset=UTF-8], content-length=[33]}, simpSessionAttributes={ip=/127.0.0.1:59629}, simpHeartbeat=[J#147a2bf, contentType=text/plain;charset=UTF-8, lookupDestination=/chatchannel, simpSessionId=79431feb8b5f4a9497492ccc64f8965f, simpDestination=/app/chatchannel}]
But if you use SimpleMessageConverter, the content-type header is not add.
this is a clue about what you want to do?
Check this question, I put some code that could help you:
How configure the Spring Sockjs Java Client message converters

Resources