logging tracking id in web filter - spring

Blockquote
Running below WebFilter I can see that:
foo log does not have tracking id
bar log has tracking id
Why is that?
#Slf4j
#Component
#Order(Ordered.HIGHEST_PRECEDENCE + 2)
class MyFilter implements WebFilter {
#Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
return Mono.just(exchange)
.doOnNext(x -> log.debug("foo"))
.flatMap(chain::filter)
.doOnSuccess(x -> log.debug("bar"));
}
}
This question refers to:
https://github.com/spring-cloud/spring-cloud-sleuth/issues/1666

Using Order annotation I register my bean before TraceWebFilter, that's why tracking is not passed to filter "out of the box".
Documentation for TraceWebFilter:
https://javadoc.io/static/org.springframework.cloud/spring-cloud-sleuth-core/2.2.8.RELEASE/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.html#ORDER

Related

Properties in application.properties not getting loaded in Filter class

I am trying to read the value from application.properties in one of the util library Filter we write.
My code is as below.
public class AuthorizationFilter extends GenericFilterBean
{
#Value ("${application.identifier}")
private String appId;
...
}
However the value appId is not read from application.properties though it is defined.
The issue occurs only with Filter classes.
Any pointers on how to fix this?
Like #M.Deinum said , If you let spring-boot manage the life cycle of the filter bean, then you will be able use the #Value annotation :
#Component
#Order(1)
public class CustomFilter implements Filter {
#Value ("${application.identifier}")
private String appId;
#Override
public void doFilter
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
LOG.info(
"Starting for req : {}",
req.getRequestURI());
chain.doFilter(request, response);
LOG.info(
"Anything");
}
// other methods
}
Keep in mind that if you provide your filter this way , you won't have to register it manually and if you want it to work for a particular url by registering it manually remember to remove the #Component annotation so that spring-boot won't take it up automatically.
Let the spring manage your filter class. You can register in your filter class like below :
#Bean
public FilterRegistrationBean registerFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(this);
registration.addUrlPatterns("/*");
return registration;
}

Apache camel dynamic routing

I have following Apache camel rest service(/sales) that internally calls another rest service(/getOrders) and get list of objects. Am able to print JSON response in the processor but getting java objects in response while trying from postman. Could anyone pls help me to resolve the issue. Attaching the response log for ref..
#Component
public class ApplicationResource extends RouteBuilder {
#Autowired
private OrderService service;
#BeanInject
private OrderProcessor processor;
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").port(9090).host("localhost");
rest().get("/getOrders").produces(MediaType.APPLICATION_JSON_VALUE).route().setBody(() -> service.getOrders());
rest().get("/sales").produces(MediaType.APPLICATION_JSON_VALUE).route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true").convertBodyTo(String.class).marshal()
.json(JsonLibrary.Jackson, Order.class).to("log:foo?showHeaders=true");;
;
}
}
You should remove the last .endRest() on "direct:bye" route.
I think you get the rest response before calling your Processor.
This works for me.
First, I needed to set the bindingMode as RestBindingMode.json in the restConfiguration.
Secondly, instead of marshal(), you need to use unmarshal().
Third, since you are returning a list of orders, .json(JsonLibrary.Jackson, Order.class) will not be sufficient to unmarshal the list of orders. You need to use a custom format which will be able to unmarshal the list of orders into a json array. This you need to do using JacksonDataFormat format = new ListJacksonDataFormat(Order.class);
#Override
public void configure() {
JacksonDataFormat format = new ListJacksonDataFormat(Order.class);
restConfiguration().component("servlet").port(9090).host(localhost).bindingMode(RestBindingMode.json);
rest()
.get("/getOrders")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getMessage().setBody(service.getOrders());
}})
.to("log:getOrders?showHeaders=true&showBody=true");
rest()
.get("/sales")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true")
.unmarshal(format)
.to("log:sales?showHeaders=true&showBody=true");
}
Solvedddd !!! i did two things as follows,May be use full for some one
1,bindingMode(RestBindingMode.auto) - RestBindingMode changes to auto
from json
2, Added this in the main
service(/getOrders).marshal().json(JsonLibrary.Jackson);
#Component
public class ApplicationResource extends RouteBuilder {
#Autowired
private OrderService service;
#BeanInject
private OrderProcessor processor;
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").port(9090).host("localhost").bindingMode(RestBindingMode.auto);
rest().get("/getOrders").produces(MediaType.APPLICATION_JSON_VALUE).route().setBody(() -> service.getOrders())
.marshal().json(JsonLibrary.Jackson);
rest().get("/sales").produces(MediaType.APPLICATION_JSON_VALUE).route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true").convertBodyTo(String.class)
.log("body = ${body}");
;
;
}
}

Spring cloud gateway: How to create a filter

I'm new to spring cloud gateway.
I've been watching some of the youtube videos from the SpringDeveloper channel and am working on the following example:
#Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/get")
.addRequestHeader("X-SpringOne", "Awesome")
.uri("http://httpbin.org:80"))
.build();
}
Prior to looking at spring cloud gateway, i've also looked at Spring Netflix Zuul. I understand that in Netflix Zuul, you can create filters by creating a class that extends ZuulFilter and define it as a pre, post, route, etc.
However I was wondering how one can create a PRE/ POST filter using Spring cloud gateway?
Any help/ advice is much appreciated.
Thanks.
For a pre filter here is AddRequestHeader (code is executed before chain.filter() call):
public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
#Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest().mutate()
.header(config.getName(), config.getValue())
.build();
return chain.filter(exchange.mutate().request(request).build());
};
}
}
For a 'post' filter, here is SetStatus (code is run in lambda in chain.filter(exchange).then()):
public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory<SetStatusGatewayFilterFactory.Config> {
#Override
public GatewayFilter apply(Config config) {
final HttpStatus status = ServerWebExchangeUtils.parse(config.status);
return (exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// check not really needed, since it is guarded in setStatusCode,
// but it's a good example
if (!exchange.getResponse().isCommitted()) {
setResponseStatus(exchange, status);
}
}));
};
}
}
Here is a simple example in Kotlin: the URI http://.../customers is mapped to the URI obtained from the discovery service (lb = load balanced) for the service named customer and appended with "/". Furthermore, the forwarded request is enhanced with an additional header entry. Hope this helps.
#SpringBootApplication
class Application {
#Bean
fun routes(builder: RouteLocatorBuilder) = builder.routes {
route {
path("/customers")
filters {
setPath("/")
addRequestHeader("aKey", "aValue")
}
uri("lb://customer")
}
}
}
I am not sure this is the correct way to do it because I am also trying to achieve this behavior, I am thinking if this is something that needs to be done:
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class CustomFilter implements GatewayFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//code for PRE filter
Mono<Void> v = chain.filter(exchange);
//code for POST filter
return v;
}
}
Let me know if that works for you or if you found another solution.

Add more field into header using interceptor of spring boot not work

I'm using spring boot.
I want to add a field into header of every response. So that, i using interceptor. The code is:
#Component
public class ApiVersionInterceptor extends HandlerInterceptorAdapter{
private final Logger log = LoggerFactory.getLogger(ApiVersionInterceptor.class);
#Autowired
private Environment environment;
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception arg3) throws Exception {
String apiVersion = environment.getProperty(ApiVersion.VERSION_KEY.getKey());
log.debug("api-version:"+apiVersion);
response.addHeader("Api-Version", apiVersion);
}
}
And the configuration is:
#Configuration
public class InterceptorsConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private ApiVersionInterceptor apiVersionInterceptor;
/**
* Add interceptor
*/
#Override
public void addInterceptors(final InterceptorRegistry registry) {
//Add api-version field to header of response
registry.addInterceptor(apiVersionInterceptor);
}
}
To make sure this snipped code is run because of:
2017-12-06 02:35:10,392 DEBUG [] [http-nio-8080-exec-7] ApiVersionInterceptor: api-version:1.9.0
But i don't understand, i don't see this field in the header of any response.
Update
My app use Restful webservice, so don't have view phase.
Thanks for help.
You should add header in a earlier phase, override the preHandle method in your ApiVersionInterceptor. Because in afterCompletion response is already committed and skip header changes.

Interceptor in Spring 5 WebFlux

I am using Spring WebFlux in my project. I want to create an interceptor to calculate the time taken by each API. In Spring MVC we have HandlerInterceptor which is not present in spring-boot-starter-webflux. I tried adding spring-boot-starter-web and wrote my interceptor but it didn't work. Here is the code:
#Component
public class TimeInterceptor implements HandlerInterceptor {
public static Logger logger = Logger.getLogger(TimeInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long totaltime = System.currentTimeMillis() - (long) request.getAttribute("startTime");
request.setAttribute("totaltime", totaltime);
logger.info("Logging total time" + totaltime);
}
...
...
I want to add similar functionality to my application and intercept time taken by each call.
Thanks in advance.
If you want to handle a request when it starts and when it completes, you can use WebFilter.
Try something like this
#Component
public class CustomWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
long startTime = System.currentTimeMillis();
return chain.filter(exchange).doFinally(signalType -> {
long totalTime = System.currentTimeMillis() - startTime;
exchange.getAttributes().put("totalTime", totalTime);
System.out.println(totalTime);
});
}
}
When request processing starts all defined filters are called. Mono is returned from filter. It indicates when request processing is complete.
There is no concept of HandlerInterceptor in Spring WebFlux, but you can use your own WebFilter for that instead.
The feature you're describing sounds a lot like the metrics support provided by Actuator and Micrometer. If you'd like to try it:
Add the actuator dependency to your project
Expose the relevant endpoints (here, metrics)
Go to "/actuator/metrics and select the metric for server HTTP requests (see the reference documentation).
Micrometer offers way more and helps you to get your metrics right, like: taking into account GC pauses when measuring time, providing histograms/percentiles/..., and more.
Note: adding spring-boot-starter-web to your application will turn it into a Spring MVC application.
Use the following project as dependency as jar / ant / maven / gradle
https://github.com/TurquoiseSpace/spring-webflux-http-interceptor
<dependency>
<groupId>com.github.TurquoiseSpace</groupId>
<artifactId>spring-webflux-http-interceptor</artifactId>
<version>0.0.7</version>
</dependency>
It provides ReactiveApiInterceptor which is a custom implementation of WebFilter
If required, you can override ReactiveApiInterceptor as well, to add your own custom logic, besides having the default logic, by calling
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
// Your custom implementation, when api is hit and the request lands
super.filter(serverWebExchange, webFilterChain)
.doFinally(signalType -> {
// Your custom implementation, when request-response exchange gets completed
});
}

Resources