Dynamic metric tagging with Spring WebClient - spring

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
}
}

Related

How to get name-value pairs(Matrix Variable) in path segments in Spring webflux service using Handler

How to get name-value pairs(Matrix Variable) in path segments in Spring webflux service using Handler ?
Sample Endpoint Which I want to expose
https://www.myservice.com/myresource/a/b;size=100;
In Controller based approach we can get it using #MatrixVariable
#MatrixVariable(name="size", pathVar="b")
How can we get the value in case of we want to use Router and Handler (Functional Endpoint) instead of Controller ?
public Mono<ServerResponse> myHandler(ServerRequest request) {
//TODO get size value
}
In the controller based approach it is very easy to get these values. I am unable to find a way to get this in Handler based approach.
Searched https://spring.getdocs.org/en-US/spring-framework-docs/docs/spring-web-reactive/webflux/webflux-controller.html
but Found a dead link -> https://spring.getdocs.org/en-US/spring-framework-docs/docs/spring-web-reactive/webflux/webflux-controller.html#webflux:webflux-controller:webflux-ann-methods.adoc#webflux-ann-matrix-variables

How to pass JSON on a GET URL with Spring Webclient

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();

Spring REST API with swagger - map of values in request param

I have a Spring Boot based REST API application with the following endpoint (Written in Kotlin)
#RequestMapping(value = ["/search"], method = [RequestMethod.GET])
#ApiOperation("Check whether any of the map values exists. Returns string 'true' if stamp exists, else 'false'")
fun checkExists(
#ApiParam("information about the stamp as key-value pairs (example: ds=2017-11-34&hh=05)", required = true)
#RequestParam searchValues: Map<String, String>
): Boolean {
return service.checkExists(searchValues)
}
And I know Spring supports sending a dynamic map of key value pairs as documented here.
I am also using Swagger to document the API definitions, and further more, I am using swagger-codegen-cli to generate the client library using which someone can connect with this REST API.
Now, the issue is, I am not able to send a map of values from the swagger generated client to the Spring REST API (even though Spring supports it). Starting from Swagger OpenAPI 3, they've added support for Object types in the specification. But this works in a different way than I need. For example with just Spring and RequestParam of type Map
http://localhost:8080/search?foo=A&bar=B
is parsed as a map of key value pairs
key="foo",value="A"
key="bar",value="B"
But, When I send a Map Object from the swagger client with the same key-value pairs
Map<String, String> values = new HashMap<>();
values.put("foo","A");
values.put("bar","B");
return out = clientApi.checkExistsUsingGET(values);
This sends a request to the REST API in form of
http://localhost:8080/search?searchValues={foo=A,bar=B}
and the map in Spring side ends up as
key="searchValues",value="{foo=A,bar=B}"
I've been struggling to get the swagger client api to send the request in a way the Spring API is intended to work with Map of values, but I am not able to figure a solution.
Am I doing using the client API in a wrong way?, or this just can't be done with swagger?
Any suggestions/opinions appreciated!
This is not yet supported by swagger-ui. See this issue https://github.com/swagger-api/swagger-ui/issues/2241

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.

How to send MultiPart HTTP request using Spring RestTemplate

I am new to Spring framework and i am learning.
My web applicaiton based out of Spring MVC needs to call a vendor service through RESTful interface.
I have current implementation for POST / GET for non multipart.
However i have a requirement to POST multipart form data consisting of JSON and Bytes
I am trying to see some smaple implementation online for reference but could not get one.
I need some idea about possible approach i can take to implement this using RestTemplate.
Thanks for reading.
if we want to send multipart form data in post request and with that if you also want some information in json format then you can create your REST call according to this. here #Consumes will say that this call will accept only MULTIPART_FORM_DATA, #Transactional is for starting new transaction.
Here i am accepting three path parameters which are
1) String jsonObj, this is a string or you can say json, in this JSON you can ask UI for required information like some tags, labels, etc
2) FormDataContentDisposition fileDetail, This will contain very basic detail of file, like fileName, contentType, etc
3) InputStream uploadedInputStream, this will contain some binary data, like image, videos, or any kind of files in byte format.
* Example *
#POST
#Path("/xyz")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public VObject postMultiPartFormData(
#FormDataParam("jsonObj") String jsonObj,
#FormDataParam("mmFile") FormDataContentDisposition fileDetail,
#FormDataParam("mmFile") InputStream uploadedInputStream) {
return new VObject();
}
I hope this will help you.

Resources