Zuul Implementing Multiple ZuulFallbackProvider for multiple zuul routes - spring

How can i implement multiple zuulFallbackProvider for multiple zuul routing.
I cant see an answer how to do it using a properties only other than exposing a restcontroller and implement a method with a hystrixcommand.
Can i make each of my service with its own zuulFallBackProvider bean?
application.yml
server:
port: 8080
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 20000
ribbon:
ReadTimeout: 20000
ConnectTimeout: 20000
zuul:
prefix: /api
ignoredServices: '*'
host:
connect-timeout-millis: 20000
socket-timeout-millis: 20000
routes:
kicks-service:
path: /kicks/**
serviceId: kicks-service
stripPrefix: false
sensitiveHeaders:
kicks-inventory:
path: /inventory/**
serviceId: kicks-inventory
stripPrefix: false
sensitiveHeaders:
This is my sample app
#SpringBootApplication
#EnableDiscoveryClient
#EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public Prefilter prefilter(){
return new Prefilter();
}
#Bean
public ZuulFallbackProvider zuulFallbackProvider() {
return new ZuulFallbackProvider() {
#Override
public String getRoute() {
return "kicks-inventory";
}
#Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
#Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
#Override
public int getRawStatusCode() throws IOException {
return 200;
}
#Override
public String getStatusText() throws IOException {
return "OK";
}
#Override
public void close() {
}
#Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
#Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
};
}
}

Each route will need a separate bean. They should return different route for getRoute method.
Please check this- http://tachniki.blogspot.in/2017/02/generic-ZuulFallbackProvider.html. Hope it will make it slightly easier.

You can have a default fallback provider for all routes, otherwise you need a fallback provider per route.
If you would like to provide a default fallback for all routes than
you can create a bean of type FallbackProvider and have the
getRoute method return * or null.
http://cloud.spring.io/spring-cloud-netflix/multi/multi__router_and_filter_zuul.html#hystrix-fallbacks-for-routes

Related

Zuul path - SpringBoot detects it although not configured

I followed the sample from the Spring Cloud docu and have now configured in my application.yml
server:
port: 18081
servlet:
context-path: /myPath/services
zuul:
# don't add per default all services automatically to the Zuul server
ignoredServices: '*'
routes:
mytest:
#path: /checkPath/**
url: http://mytest.server.local/api/
and a configured Filter:
public class PreFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
System.out.println("Request Method : " + request.getMethod() + " Request URL : " + request.getRequestURL().toString());
return null;
}
}
When I test with
http://localhost:18081/myPath/services/mytest/test
I would expect that this works only if path is configured. If I leave it as above (uncommented) I get from ZuulPreFilter that it was hit. I wouldn't expect it since the path shouldn't be found. Is there a misunderstanding or do I miss something in my config? Why is the path hit at all because .../checkPath2/... won't be detected?

Zuul API Gateway Logger filter not logging requests, returns 401 instead

I have a few micro-services powered by Ribbon, Eureka and OAuth2 auth server. Everything worked like a charm. Then I introduced Zuul API Gateway, and added a logger class to log my requests.
Zuul Gateway yml
spring:
application:
name: zuul-api-gateway-service
server:
port: 8765
eureka:
client:
service-url:
defaultZone: http://theusername:thepassword#localhost:8761/eureka
register-with-eureka: true
zuul:
sensitiveHeaders: Cookie,Set-Cookie, Authorization
routes:
job-service:
path: /api/**
url: http://localhost:7084
jobposting-service:
path: /api/**
url: http://localhost:6084
stripPrefix: false
oauth:
path: /auth/**
url: http://localhost:9191
Zuul Appplication class
#SpringBootApplication
#EnableZuulProxy
#EnableEurekaClient
public class ZuulApiGatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApiGatewayServerApplication.class, args);
}
}
LoggingFilter
#Component
public class ZuulLoggingFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public String filterType() {
return "pre"; //pre post error
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
logger.info("request -> {} request uri -> {}", request, request.getRequestURI());
//
// RequestContext ctx = RequestContext.getCurrentContext();
// // Add a custom header in the request
// ctx.addZuulRequestHeader("Authorization",
// request.getHeader("Authorization"));
// logger.info(String.format("%s request to %s", request.getMethod(),
// request.getRequestURL().toString()));
return null;
}
}
Then my API requests turned out not working. Any request, even with Authorization token attached to the header, returns 401 unauthorized. The logger not logging anything as well.
Thanks in advance

how to route to an external url using zuul proxy filter

i have an external url and i want to pass some request header through zuul filter to launch the application.
Can anyone please help me on this.
In my custom prefilter i have written this:
#Component
public class CustomFilters extends ZuulFilter {
public static final Logger logger = LoggerFactory.getLogger(CustomFilters.class);
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
logger.info("executing run ");
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("x-forwarded-host", "<external url>");
ctx.addZuulRequestHeader("x-forwarded-proto", "https");
ctx.addZuulRequestHeader("x-forwarded-port", "8800");
ctx.addZuulRequestHeader("key", "id);
return null;
}
}
app.properties:
ribbon.eureka.enabled=false
server.port=8080
zuul.routes.books.sensitive-headers=
zuul.routes.books.path = /books/
zuul.routes.books.url = <ext url>
Sample Application:
This is giving me a rest url through which i am redirecting to external url defined above in my properties file.
public class SampleApplication {
#RequestMapping(value = "/check")
public String available() {
return "available!";
}
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
app.properties:
spring.application.name=book
server.port=8090
Issue screenshot
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
These simple url-routes do not get executed as a HystrixCommand, nor do they load-balance multiple URLs with Ribbon. To achieve those goals, you can specify a serviceId with a static list of servers, as follows:
zuul:
routes:
echo:
path: /myusers/**
serviceId: myusers-service
stripPrefix: true
hystrix:
command:
myusers-service:
execution:
isolation:
thread:
timeoutInMilliseconds: ...
myusers-service:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
ListOfServers: http://example1.com,http://example2.com
ConnectTimeout: 1000
ReadTimeout: 3000
MaxTotalHttpConnections: 500
MaxConnectionsPerHost: 100
in your filter make sure that this filter should come into picture only for request with uri example1.com or example2.com by impelemting should filter method

How to make Zuul dynamic routing based on HTTP method and resolve target host by 'serviceId'?

How to make Zuul dynamic routing based on HTTP method (GET/POST/PUT...)?
For example, when you need to route the POST request to the different host instead of the default one described in 'zuul.routes.*'...
zuul:
routes:
first-service:
path: /first/**
serviceId: first-service
stripPrefix: false
second-service:
path: /second/**
serviceId: second-service
stripPrefix: false
I.e. when we request 'GET /first' then Zuul route the request to the 'first-service', but if we request 'POST /first' then Zuul route the request to the 'second-service'.
To implement dynamic routing based on HTTP method we can create a custom 'route' type ZuulFilter and resolve 'serviceId' through DiscoveryClient. Fore example:
#Component
public class PostFilter extends ZuulFilter {
private static final String REQUEST_PATH = "/first";
private static final String TARGET_SERVICE = "second-service";
private static final String HTTP_METHOD = "POST";
private final DiscoveryClient discoveryClient;
public PostOrdersFilter(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String method = request.getMethod();
String requestURI = request.getRequestURI();
return HTTP_METHOD.equalsIgnoreCase(method) && requestURI.startsWith(REQUEST_PATH);
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
List<ServiceInstance> instances = discoveryClient.getInstances(TARGET_SERVICE);
try {
if (instances != null && instances.size() > 0) {
context.setRouteHost(instances.get(0).getUri().toURL());
} else {
throw new IllegalStateException("Target service instance not found!");
}
} catch (Exception e) {
throw new IllegalArgumentException("Couldn't get service URL!", e);
}
return null;
}
}
#Cepr0's solution is right. Here I am proposing just a simpler way (without service discovery). Assuming you have that route:
zuul:
routes:
first:
path: /first/**
# No need for service id or url
Then you can route requests for '/first' route in 'route' type filter just by setting location to request context.
#Component
public class RoutingFilter extends ZuulFilter {
#Override
public String filterType() {
return ROUTE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
/* Routing logic goes here */
URL location = getRightLocationForRequest();
ctx.setRouteHost(location);
return null;
}
}

Spring cloud Eureka server is NOT replicating each other, displaying warning

I am using two Eureka server in spring cloud to replicate each other, when I open the page at http://localhost:8761, I saw this message:
RENEWALS ARE LESSER THAN THE THRESHOLD. THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
The eureka application.xml is this:
server:
port: ${server.instance.port:5678}
spring:
application:
name: nodeservice
sidecar:
port: ${nodeserver.instance.port:3000}
health-uri: http://localhost:${nodeserver.instance.port:3000}/health.json
eureka:
instance:
hostname: ${nodeserver.instance.name:localhost}
preferIpAddress: ${preferipaddress:false}
leaseRenewalIntervalInSeconds: 5 #default is 30, recommended to keep default
metadataMap:
instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
So if I go to http://localhost:8761, I see all the services registered, but if I go to http://localhost:8762, I then see no micro-service registered.
Any idea why?
Eureka: register only the first success url. In your case the first success url is http://localhost:8761/eureka/ so it's not continue to register the next url http://localhost:8762/eureka/.
You can override that by:
Application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
additionalZones: http://localhost:8762/eureka
Your Application.java
#SpringBootApplication
#EnableEurekaClient
public class Application implements ApplicationContextAware {
#Value("${eureka.client.serviceUrl.additionalZones:}")
String additionalZones;
ConfigurableApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Map<String, EurekaClient> additionalEurekaClients(ApplicationInfoManager manager,
#Autowired(required = false) HealthCheckHandler healthCheckHandler) {
HashMap clients = new HashMap<>();
if(Text.isEmpty(additionalZones))
return clients;
String[] hosts = additionalZones.split(",");
for(int i=0; i < hosts.length; i++)
{
EurekaClient client = new CloudEurekaClient(manager, new SimpleEurekaClientConfig(hosts[i].trim(),"defaultZone"), null,
this.applicationContext);
client.registerHealthCheck(healthCheckHandler);
String clientName = "client_"+ (i+1);
clients.put(clientName, client);
}
return clients;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
#PreDestroy
public void unRegisterInAllConfiguredDiscovery() {
Map<String, EurekaClient> additionalEurekaClients = this.applicationContext.getBean("additionalEurekaClients", Map.class);
additionalEurekaClients.forEach((k, v) -> v.shutdown());
}
}
SimpleEurekaClient.java
package com.netflix.eureka;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import java.util.Arrays;
import java.util.List;
public class SimpleEurekaClientConfig extends EurekaClientConfigBean {
private String eurekaUrl;
private String zone;
private String region = "us-east-1";
public SimpleEurekaClientConfig(String eurekaUrl, String zone, String region) {
this.eurekaUrl = eurekaUrl;
this.zone = zone;
this.region = region;
}
public SimpleEurekaClientConfig(String eurekaUrl, String zone) {
this.eurekaUrl = eurekaUrl;
this.zone = zone;
}
#Override
public String getRegion() {
return region;
}
#Override
public String[] getAvailabilityZones(String s) {
return new String[] {zone};
}
#Override
public List<String> getEurekaServerServiceUrls(String s) {
return Arrays.asList(eurekaUrl);
}
#Override
public boolean shouldEnforceRegistrationAtInit() {
return true;
}
#Override
public boolean shouldRegisterWithEureka() {
return true;
}
}
It's not a XML you need rename it to "application.YML"
https://en.wikipedia.org/wiki/YAML

Resources