spring sleuth baggage propagation not getting propagated/working - spring

We are currently using sleuth 2.2.3.RELEASE, and we couldn't see the field userId passed in http headers are not propagating. Below is our code.
BaggageField REQUEST_ID = BaggageField.create("x-vcap-request-id");
BaggageField USER_ID = BaggageField.create("userId");
Tracing.newBuilder().propagationFactory(
BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY)
.add(SingleBaggageField.remote(REQUEST_ID))
.add(SingleBaggageField.newBuilder(USER_ID).addKeyName("baggage-user-id").build())
.build());
We doubt that some issue in YML file. We tried with all the below options but none is working.
#1 baggage-keys: baggage-user-id
#2 propagation-keys: baggage-user-id
#3 baggage-keys: user-id
In logback:
%X{baggage-user-id:-}
We are passing userId as in http header.

Please do not create your own instance of tracing. You can create beans that will end up inside the tracing bean.
Here you have an example that uses the latest 3.x api
spring:
sleuth:
baggage:
correlation-fields:
- TEST-COMMUNICATION-TYPE
remote-fields:
- TEST-COMMUNICATION-TYPE
Old, deprecated api
spring:
application:
name: service1
sleuth:
baggage-keys:
- baggage
- key
log.slf4j.whitelisted-mdc-keys:
- key
How we retrieve baggage
log.info("Service2: Baggage for [key] is [" + BaggageField.getByName("key") + "]");
How we set the baggage
String baggageKey = "key";
String baggageValue = "foo";
BaggageField baggageField = BaggageField.create(baggageKey);
baggageField.updateValue(baggageValue);

Related

Configuring Swagger UI for OAuth 2.0 in Spring Boot with Kotlin

I am trying to configure OpenAPI 3 for OAuth 2.0 with a configuration class in Spring Boot with Kotlin.
Even though I set oauth2RedirectUrl in application.yml, when I click authorize in swagger UI to get new token to send a request, redirect url doesn't work as expected and I get the default redirect url called something like that(I believe it's a default redirectUrl): &redirect_uri=http://localhost:8080/oauth2-redirect.html instead of (what i configured in application.yaml)
Access the Swagger-UI at http://localhost:8080/swagger-ui/index.html?queryConfigEnabled=true&url=/v3/api-docs
Then click the authorize button and use the preconfigured values.
The IdentityProviderController prints then the configured values, e.g. redirect_uri.
The redirect_uri looks like http://localhost:8080/swagger-ui/oauth2-redirect.html and the swagger-ui:oauth2RedirectUrl path is missing. Even when it is configured in the application.yaml.
I added the following dependencies:
implementation("org.springdoc:springdoc-openapi-ui:1.6.14")
implementation("org.springdoc:springdoc-openapi-kotlin:1.6.14")
implementation("org.springdoc:springdoc-openapi-security:1.6.14")
and this is my application.yml
springdoc:
api-docs:
enabled: true
swagger-ui:
query-config-enabled: true
oauth:
client-id: <clientId>
client-secret: <clientSecret>
use-pkce-with-authorization-code-grant: true
oauth2RedirectUrl: <redirectUrl>
and this here is my configuration class:
#Configuration
#OpenAPIDefinition
#SecurityScheme(
name = "oauth2",
type = SecuritySchemeType.OAUTH2,
flows =
OAuthFlows(
authorizationCode =
OAuthFlow(
authorizationUrl = "<authorizationUrl>",
tokenUrl = "<tokenUrl>",
scopes =
[
OAuthScope(name = "test1"),
OAuthScope(name = "test2"),
OAuthScope(name = "test3")],
)))
open class OpenApiConfiguration {
#Bean
open fun customOpenAPI(): OpenAPI {
return OpenAPI()
.components(Components())
.info(
Info()
.title("ABC Service Rest API")
.description("description...")
.version("1.0.0"))
}
}
What am I missing here?
UPDATE: (17.02.2023)
After I am changing the redirect_uri in chrome with the correct one, then I can reach the Identity proverders' page, so I only need to find a way to set my redirectUrl configuration properly.

SpringBoot 2.6.2 and form data

I am experimenting with a controller endpoint that looks like this:
#PostMapping("login")
fun login(
#RequestParam username: String,
#RequestParam password: String): ResponseEntity<LoginResponse> {
// ...
}
The request is send from a HTML form looking like this:
<form action="../api/login" method="POST">
<input id="username" type="text" placeholder="Enter Username" name="username" required=""><br>
<input id="password" type="password" placeholder="Enter Password" name="password" required=""><br>
<button type="submit">Login</button>
</form>
This works perfectly will with spring boot version 2.6.1. But after an upgrade to version 2.6.2 and adding spring cloud gateway it all of a sudden does not work any longer.
The log would look like this:
2022-01-11 14:33:09,618 [reactor-http-nio-2] DEBUG o.s.web.method.HandlerMethod - [3d97dc1a-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:13027] Could not resolve parameter [0] in public org.springframework.http.ResponseEntity<com.example.models.LoginResponse> com.example.login(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String): 400 BAD_REQUEST "Required String parameter 'username' is not present"
2022-01-11 14:33:09,656 [reactor-http-nio-2] DEBUG org.springframework.web.HttpLogging - [3d97dc1a-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:13027] Resolved [ServerWebInputException: 400 BAD_REQUEST "Required String parameter 'username' is not present"] for HTTP POST /api/login
I tried various things like:
#PostMapping(value = ["login"], consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun login(
#RequestParam paramMap: MultiValueMap<String,String>
): ResponseEntity<LoginResponse> {
//...
}
But also this fails with the following log:
2022-01-11 14:10:11,589 [reactor-http-nio-2] DEBUG o.s.w.s.a.HttpWebHandlerAdapter - [656327b1-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:11772] HTTP POST "/api/login"
2022-01-11 14:10:11,601 [reactor-http-nio-2] DEBUG o.s.w.r.r.m.a.RequestMappingHandlerMapping - [656327b1-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:11772] Mapped to com.example.ApiController#login(MultiValueMap)
2022-01-11 14:10:12,945 [reactor-http-nio-2] DEBUG o.s.w.r.r.m.a.RequestBodyMethodArgumentResolver - Form data is accessed via ServerWebExchange.getFormData() in WebFlux.
2022-01-11 14:10:21,640 [reactor-http-nio-2] DEBUG o.s.web.method.HandlerMethod - [656327b1-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:11772] Could not resolve parameter [0] in public org.springframework.http.ResponseEntity<com.example.models.LoginResponse> com.example.ApiController.login(org.springframework.util.MultiValueMap<java.lang.String, java.lang.String>): 415 UNSUPPORTED_MEDIA_TYPE
I would guess the error message with 415 UNSUPPORTED_MEDIA_TYPE is just misleading and it somehow fails to map the form-data. What can I do to get the API again accept form-data?
Trying something like:
#PostMapping(value = ["login"], consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun login(#RequestParam paramMap: Map<String,String>): ResponseEntity<LoginCodeResponse> {
// ...
}
Actually get's called but paramMap is always empty.
What actually works is the following:
#RestController
#RequestMapping("api")
class HelloWorldController(){
#GetMapping("hello")
fun helloName(#RequestParam name: String): String {
return "Hello $name!"
}
}
So for a normal get request #RequestParam works as expected.
Update
I seems to boil down to the following. With spring-boot-starter-webflux it seems the #RequestParam for form-data does not work. This seem to be a known issue.
implementation("org.springframework.boot:spring-boot-starter-webflux")
With spring-boot-starter-web it #RequestParam for form-data works.
implementation("org.springframework.boot:spring-boot-starter-web")
But this starter is not compatible with spring cloud. Using both spring-boot-starter-web with setting spring.main.web-application-type=reactive makes spring cloud gateway start with spring-boot-starter-web but still #RequestParam for form-data not working.
To get from data with POST working with webflux I did the following using the nicely provided awaitFormData method:
#PostMapping("authorize", consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
suspend fun login(exchange: ServerWebExchange): ResponseEntity<LoginResponse> {
val formData = exchange.awaitFormData()
val username = formData["username"]?.get(0)!!
val password = formData["password"]?.get(0)!!
That is just a sketch of the essentials of the solution. If you want to use this you should also add some proper error handling to check whether parameters are there otherwise you will just have an error 500 which does not tell a lot.

Health Endpoint Metrics not being exported after Spring Boot 2 migration

My Team migrated our Microservices from Spring Boot 1 to Version 2 and since the Actuator changed, our Health Endpoint Metrics exported via prometheus jmx exporter do not work anymore.
The usual /actuator/health is working as expected, but the prometheus-jmx-exporter won't pick it up although several things tried:
I changed the Metainformation in the exporter-config.yaml to reflect the name change in Spring Boot 2
I added the io.micrometer:micrometer-registry-prometheus to our build.gradle to see if this is the issue
I exposed web and jmx endpoints acording to the Spring Boot 2 Documentation
So now I run out of ideas and would appreciate any hints oyu might be able to give me
old prometheus-jmx-exporter exporter-config.yaml:
---
lowercaseOutputName: true
lowercaseOutputLabelNames: true
whitelistObjectNames: ["org.springframework.boot:type=Endpoint,name=healthEndpoint"]
rules:
- pattern: 'org.springframework.boot<type=Endpoint, name=healthEndpoint><(.*, )?(.*)>(.*):'
name: health_endpoint_$1$3
attrNameSnakeCase: true
new prometheus-jmx-exporter exporter-config.yaml:
---
lowercaseOutputName: true
lowercaseOutputLabelNames: true
whitelistObjectNames: ["org.springframework.boot:type=Endpoint,name=Health"]
rules:
- pattern: 'org.springframework.boot<type=Endpoint, name=Health>'
name: health_endpoint_$1$3
attrNameSnakeCase: true
current application properties about actuator endpoints:
management.endpoints.web.exposure.include=info, health, refresh, metrics, prometheus
management.endpoints.jmx.exposure.include=health, metrics, prometheus
in Spring Boot 1 with the old exporter-config.yaml I get results like this:
# HELP health_endpoint_hystrix_status Invoke the underlying endpoint (org.springframework.boot<type=Endpoint, name=healthEndpoint><hystrix, status>status)
# TYPE health_endpoint_hystrix_status untyped
health_endpoint_hystrix_status 1.0
# HELP health_endpoint_status Invoke the underlying endpoint (org.springframework.boot<type=Endpoint, name=healthEndpoint><status>status)
# TYPE health_endpoint_status untyped
health_endpoint_status 1.0
But with all the changes and in Spring Boot 2 I get nothing out of this.
You can cofigure your own health value and add it to the Prometheus Metrics endpoint:
#Configuration
public class HealthMetricsConfiguration {
#Bean
public MeterRegistryCustomizer prometheusHealthCheck(HealthEndpoint healthEndpoint) {
return registry -> registry.gauge("health", healthEndpoint, HealthMetricsConfiguration::healthToCode);
}
public static int healthToCode(HealthEndpoint ep) {
Status status = ep.health().getStatus();
return status.equals(Status.UP) ? 1 : 0;
}
}

Google Maps API with different results between Chrome and RestTemplate

The service is consuming google maps api (geocode).
When I execute a GET using default bean configuration for spring resttemplate, I have a value different from when I execute this GET on web browser (Chrome).
Call on Chrome and using resttemplate:
https://maps.googleapis.com/maps/api/geocode/json?key=mykeymykeymykeymykey&address=Rua%20Marques%20de%20Valenca,%20100,%20Alto%20da%20Mooca,%20S%C3%A3o%20Paulo%20-%20SP,%20Brasil&language=pt-BR
When I execute a reverse geocode, the chrome execution is more precise.
Results:
Chrome:
location: {
lat: -23.5577251,
lng: -46.5948733
},
RestTemplate:
location: {
lat: -23.5574375,
lng: -46.5948733
},
I´ve tried use Double, Float and BigDecimal. And I try create a deserializer to get this value before the serialization, but the value is the same.
I´m using Java 8 with Spring Boot 2.0.3.
Anyone knows how to accurate it?
I was using UriComponentsBuilder and when I use toUriString the url was formatted to browser and it was not working correctly.
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(googleHost)
.queryParam("key", apiKey)
.queryParam(input, address)
.queryParam("language", language);
It´s working now using StringUtils.join(...) to build the URI.
String googleurl = StringUtils.join(googleGeocodingHost,
"?key=", apiKey, "&", input, "=", address, "&language=", language);

Spring Cloud Stream w/Kafka + Confluent Schema Registry Client broken?

Curious if anyone has got this working as I'm currently struggling.
I have created simple Source and Sink applications to send and receive an Avro schema based message. The schema for the message is held in a Confluent Schema Registry. Both apps are configured to use the ConfluentSchemaRegistryClient class but I think there might be a bug in here somewhere. Here's what I see that makes me wonder.
If I interact with the Confluent registry's REST API I can see that there is only one version of the schema in question (lightly edited to obscure what I'm working on):
$ curl -i "http://schemaregistry:8081/subjects/somesubject/versions"
HTTP/1.1 200 OK
Date: Fri, 05 May 2017 16:13:37 GMT
Content-Type: application/vnd.schemaregistry.v1+json
Content-Length: 3
Server: Jetty(9.2.12.v20150709)
[1]
When the Source app sends off its message over Kafka I noticed that the version in the header looked a bit funky:
contentType"application/octet-stream"originalContentType/"application/vnd.somesubject.v845+avro"
I'm not 100% clear about why the application/vnd.somesubject.v845+avro content type is wrapped up in application/octet-stream but ignoring that, note that it is saying version 845 not version 1.
Looking at the ConfluentSchemaRegistryClient implementation I see that it POSTs to /subjects/(string: subject)/versions and returns the id of the schema not the version. This then gets put into SchemaReference's version field: https://github.com/spring-cloud/spring-cloud-stream/blob/master/spring-cloud-stream-schema/src/main/java/org/springframework/cloud/stream/schema/client/ConfluentSchemaRegistryClient.java#L81
When the Sink app tries to fetch the schema for the message based upon the header it fails because it tries to fetch version 845 that its plucked out of the header: https://github.com/spring-cloud/spring-cloud-stream/blob/master/spring-cloud-stream-schema/src/main/java/org/springframework/cloud/stream/schema/client/ConfluentSchemaRegistryClient.java#L87
Anyone have thoughts on this? Thanks in advance.
** UPDATE **
OK pretty convinced this is a bug. Took the ConfluentSchemaRegistryClient and modified the register method slightly to POST to /subjects/(string: subject) (i.e. dropped the trailing /versions) which per Confluent REST API docs returns a payload with the version in it. Works like a charm:
public SchemaRegistrationResponse register(String subject, String format, String schema) {
Assert.isTrue("avro".equals(format), "Only Avro is supported");
String path = String.format("/subjects/%s", subject);
HttpHeaders headers = new HttpHeaders();
headers.put("Accept",
Arrays.asList("application/vnd.schemaregistry.v1+json", "application/vnd.schemaregistry+json",
"application/json"));
headers.add("Content-Type", "application/json");
Integer version = null;
try {
String payload = this.mapper.writeValueAsString(Collections.singletonMap("schema", schema));
HttpEntity<String> request = new HttpEntity<>(payload, headers);
ResponseEntity<Map> response = this.template.exchange(this.endpoint + path, HttpMethod.POST, request,
Map.class);
version = (Integer) response.getBody().get("version");
}
catch (JsonProcessingException e) {
e.printStackTrace();
}
SchemaRegistrationResponse schemaRegistrationResponse = new SchemaRegistrationResponse();
schemaRegistrationResponse.setId(version);
schemaRegistrationResponse.setSchemaReference(new SchemaReference(subject, version, "avro"));
return schemaRegistrationResponse;
}

Resources