Setting the route programmatically in Spring Cloud Netflix Zuul - spring

I have created two AWS Beanstalk envs, each with their own version of the applications. The urls for these envs are https://beta.myserver.com/v1073 and https://beta.myserver.com/v1084. These urls point to the load balancer.
Now I also have a Zuul implementation that have the following configurations.
zuul:
routes:
beta:
path: /api/**
serviceId: beta-root
strip-prefix: false
sensitive-headers: Cookie,Set-Cookie
ribbon:
eureka:
enabled: false
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
beta-root:
ribbon:
listOfServers: https://beta.myserver.com
Request for my application must have a version header. I have a Zuul "pre" filter that inspect this header value. The goal is to route the request based on the header value.
I have been able to intercept the request, but have not been able to route it from the filter. The code snippet is below. After running the code the request still tries to go https://beta.myserver.com/api/...
#Override
public Object run() {
/* Logic to get version header etc */
/* Set the new route */
Map<String, ZuulRoute> routes = zuulProps.getRoutes();
ZuulRoute currentRoute = routes.get("beta-root");
currentRoute.setLocation("https://beta.myserver.com/v1073");
/* Refresh the route */
routeLocator.getRoutes();
logger.warn("Current Route:" + currentRoute.getLocation());
return null;
}
Any suggestion how to resolve this issue?

What you really need to do is that changing requestURI, not server location in your case. You can easily do that like below.
First, your filter's order should be bigger than PreDecorationFilter's order that is currently 5 in Dalston release. (You need to check the value from the release you're using).
PreDecorationFilter processes request header and fill necessary info into RequestContext. And this RequestContext will be used to define actual URL for your request. requestURI is the key that you need to change. In you case, you can append or modify requestURI based on headers. The below is snippet for your pre-filter.
#Override
public int filterOrder() {
return 6;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// override request URI
ctx.set("requestURI", "/v1073" + ctx.get("requestURI"));
return null;
}
Your original implementation seems to have some problems. First, you're trying to change location in ZuulProperties route. This object is shared for all requests. If you change it just for a specific request, other request that may have different headers could be routed to wrong location.
Second, you're setting location property in ZuulRoute object like below.
currentRoute.setLocation("https://beta.myserver.com/v1073");
Originally location property value is your service-id -beta-root - with your configuration. If you change it with a specific url that has 'http' or 'httpsprefix, originalRibbonRoutingFilterwill not work. Instead,SimpleRoutingHostFilterwill process your request. It means that your not-modified requests will be handled byRibbonRoutingFilterand modified requests will be handled bySimpleHostRoutingFilter`.
Update : To route different hosts based on Http Header
If you want to route to different hosts based on http headers, there are several ways to do that.
Case 1: In case that you are using Ribbon (and RibbonRoutingFilter)
The following feature only works on Edgware.SR1 and later version. From Eddware.SR1, you can put specify value for loadbalancer called FilterConstants.LOAD_BALANCER_KEY in RequestContext. This value will be passed into Ribbon load balancer. You can put any value(any object) that you want in your prefilter. If you want to change route based on a special http header, you can do that in your custom prefilter.
And then define your own IRule implementation for Ribbon.LOAD_BALANCER_KEY will be given to your IRule implementation. Therefore you can choose the specific server from the list of server that Ribbon has based on the value of LOAD_BALANCER_KEY that you set.
You can find brief documentation here.
You can find sample code from the test case in PR. (RibbonRoutingFilterLoadBalancerKeyIntegrationTests.java)
Case 2: In case that you are using SimpleHostRoutingFilter (without Ribbon)
If you specify url instead of serviceId in zuul's route properties, the request will be routed by SimpleHostRoutingFilter without Ribbon.
SimpleHostRoutingFilter just use the host address by the below code
RequestContext.getCurrentContext().getRouteHost();
This value is set by PreDecorationFilter. So you can change this info in your prefilter.
Make your own custom prefilter that has the order value between PreDecorationFilter's and SimpleHostRoutingFilter's.
Inside your filter, check route host. If it is any known host that you want to change it based on HTTP header, change the route host via RequestContext.getCurrentContext().setRouteHost based on Http Header.
In case of the second approach, I didn't try to do that by myself. It's just theoretical solution that I think.
The problem of the second approach is that it is using SimpleHostRoutingFilter. SimpleHostRoutingFilter doesn't make any HystrixCommand for the request, so you can't use any circuit breaker features that is provided by Hystrix inside Zuul. If you are using Edgware release, the first approach is better as I think.

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

Azure Functions proxy to url provided by querystring gives 404

I've set up an Azure Functions proxy (using proxies.json). This should just pick the value given in the original request's url query string parameter and use that as a value for backendUri. So the goal is that the response of the call to the proxy contains the response of calling the URL that's in the url query string parameter directly. I need this because of CORS.
Here's my proxies.json
{
"$schema": "http://json.schemastore.org/proxies",
"proxies": {
"proxy1": {
"debug": true,
"matchCondition": {
"methods": ["GET"],
"route": "/proxy/"
},
"backendUri": "{request.querystring.url}"
}
}
}
When I call the proxy using https://not-an-actual-url.azurewebsites.net/proxy/?url=https://stackoverflow.com I'm getting back a 404. Same if I encode the value of the url parameter. If I set the backendUri in proxies.json to a static URL instead of trying to use the query string, it works, however.
To summarize, I want the value of backendUri to depend on the URL of the original request. As stated in the docs this should be possible. Quote from the docs:
Set the backend URL to another endpoint. This endpoint could be a function in another function app, or it could be any other API. The value does not need to be static, and it can reference application settings and parameters from the original client request.
When I call the proxy using
https://not-an-actual-url.azurewebsites.net/proxy/?url=https://stackoverflow.com
I'm getting back a 404. Same if I encode the value of the url
parameter. If I set the backendUri in proxies.json to a static URL
instead of trying to use the query string, it works, however.
Judging from your problem description, you don't seem to have a real HttpTrigger. You want to use function app as a server to forward requests to an address, right?
I think it is unrealistic that you want to dynamically get the url from the request and apply it to proxies.json. Because this file is already loaded when the function app is started, you cannot let the requested information enter, it will read your value as a normal string, if it is not a direct url, it cannot be read.
For CORS, you can find some free and public servers for forwarding, or build a server for forwarding by yourself. The proxies.json of function app may not realize your idea.

Spring Boot 2.2.5. How to retrieve a PathVariable parameter inside a Spring Filter

The problem I want to solve.
I need to apply a specific logic to all restful endpoints where the url belongs to a specific sub path: let's say "/api/employee/{id}". This means all the links which start with this path should apply a logic based on the employee ID, which I am trying to apply directly in Spring Boot filter in order to avoid to spread the logic everywhere.
The problem I face.
I am able to get the query parameters from the ServletRequest, but the PathVariables are not available in the Filter.
Any idea how this could be parsed?
Would be much appreciated :)
The PathVariables are simply the URI. You cann call getRequestURI()
From the docs:
java.lang.String getRequestURI()
Returns the part of this request's URL from the protocol name up to the query string in the first line of the HTTP request. The web container does not decode this String. For example:
First line of HTTP request Returned Value
POST /some/path.html HTTP/1.1 /some/path.html
GET http://foo.bar/a.html HTTP/1.0 /a.html
HEAD /xyz?a=b HTTP/1.1 /xyz
https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html#getRequestURI--

Is it possible to proxy an interface with multiple methods going to different endpoints

I have an interface with methods foo() and bar() that I'd like to go to endpoints direct:foo and direct:bar. In the proxy configuration you are only allowed to enter one endpoint and I have not found any way to get the name of the method called in code to be able to route based on that name.
Am I missing some document somewhere?
Look at the info at http://camel.apache.org/message-endpoint.html related to 'toD'
I think you are using a Camel version > 2.15
Revert to old behaviour which does not bind parameters to body and then you will have accesso to BeanInvocation object that will tell you which method was called.
// Create Proxy
MyAuditService service = new ProxyBuilder(context)
.endpoint("direct:analyzeMethodCall") // dispatcher endpoint
.binding(false) // false: gives you BeanInvocation, true gives you parameter
.build(MyAuditService.class);
Then in your route from direct:analyzeMethodCall use a Processor to analyze the BeanInvocation object and call direct:foo or direct:bar. You must set the body explicitly.

Validate request headers with Spring validation framework

Is it possible to use the Spring validation framework with Spring MVC to validate the presence and value of an HTTP request header?
To check the presence of a request header, you don't need the validation framework. Request header parameters are mandatory by default, and if a mandatory header is missing in a request, Spring MVC automatically responds with 400 Bad Request.
So the following code automatically checks the presence of the header "Header-Name"...
#PostMapping("/action")
public ResponseEntity<String> doAction(#RequestHeader("Header-Name") String headerValue) {
// ...
}
... and if the header shall be optional, the annotation would need to be replaced by:
#RequestHeader(name = "Header-Name", required = false)
To check the value of a request header, the Spring validation framework can be used. To do this, you need to
Add #Validated to the controller class. This is a workaround needed until this feature is implemented.
Add the JSR-303 annotation to the request header parameter, e.g.
#RequestHeader("Header-Name") #Pattern(regexp = "[A-Za-z]*") String headerValue
Note however that this will result in a 500 in case of an invalid header value. Check this question for how to also get the correct status code (i.e. 400) for this case.
I don't see how this would be possible, since the validation framework only operates on your domain objects, not on the HTTP request itself. Specifically, the Validator interface doesn't specify any methods that take the HttpServletRequest object, which is what you'd need to have access to in order to grab the headers and test them.
Using the validation framework feels like the wrong solution to whatever problem you're trying to solve, especially since it's hard to know how there'd be a unique HTTP request header for a given form submission. Are you looking to test for an HTTP header that should always be present in requests to your app? Then you might want to consider implementing a HandlerInterceptor, which will intercept and process all requests to pages that you've mapped in any HanderMappings. Are you looking to test for an HTTP header that should always be present in any page view of your app? Then you'd want to implement a Filter, which operates outside of the context of Spring MVC.

Resources