delivery headers by ZuulFilter fail - spring-cloud

I have overridden the run() method in a ZuulFilter as
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader('header name', 'value');
However, in another service I can't find the header from the request.
More details, filterType is "pre" and there is only one filter.

Header added in a Zuul filter,
context.addZuulRequestHeader("my-header", "my-value");
can be retrieved in another filter this way,
context.getZuulRequestHeaders().get("my-header");
I also try to keep my pre-filter order low, e.g., -1000;
You can retrieve the header in an internal service, a client of Zuul, as you would do it for any other header. There is no distinction between regular request header and Zuul header, e.g.,
#RequestMapping(value = /abc/hello, method = RequestMethod.GET)
public MyObject read(#RequestHeader("my-header") String value) {
...
}

Example filter adding a header:
public class AddHeaderFilter extends ZuulFilter {
public String filterType() {
return "pre";
}
public int filterOrder() {
return 100;
}
public boolean shouldFilter() {
return true;
}
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
context.addZuulRequestHeader('header name', "value");
return null;
}
}
Then in the application configuration you have to load the filter as a bean:
#Bean
AddHeaderFilter addHeaderFilter() {
return new AddHeaderFilter();
}

Related

Feign - define param value for each methods

I need to write a client with multiple methods that require the apiKey as query string param. Is it possible to allow the client's user to pass the api key only to the method withApiKey, so I can avoid to request the apiKey as first parameter of each method?
public interface Client {
#RequestLine("GET /search/search?key={apiKey}&query={query}&limit={limit}&offset={offset}")
SearchResponse search(#Param("apiKey") String apiKey, #Param("query") String query, #Param("limit") Integer limit, #Param("offset") Integer offset);
#RequestLine("GET /product/attributes?key={apiKey}&products={products}")
List<Product> getProduct(#Param("apiKey") String apiKey, #Param("products") String products);
public class Builder {
private String basePath;
private String apiKey;
public Client build() {
return Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.client(new ApacheHttpClient())
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.target(Client.class, basePath);
}
public Builder withBasePath(String basePath) {
this.basePath = basePath;
return this;
}
public Builder withApiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}
}
}
Depending on the setup request-interceptors might work: https://github.com/OpenFeign/feign#request-interceptors
Hopefully the example below will help.
You can swap the builder out for just the interface annotation and then move the configuration to a configuration class, if you are using spring it could be like:
#FeignClient(
name = "ClerkClient",
url = "${clery-client.url}", // instead of the withBasePath method
configuration = {ClerkClientConfiguration.class}
)
public interface Client {
Then the ClerkClientConfiguration class can define the required config beans including a ClerkClientInterceptor
public class ClerkClientConfiguration {
#Bean
public RequestInterceptor clerkClientInterceptor() {
return new ClerkClientInterceptor();
}
Then the interceptor can have a value picked up from the config and added to the queries (or header etc)
public class ClerkClientInterceptor implements RequestInterceptor {
#Value("${clerk-client.key}")
private String apiKey
#Override public void apply(RequestTemplate template) {
requestTemplate.query( "key", apiKey);
}

How can I change the feign URL during the runtime?

#FeignClient(name = "test", url="http://xxxx")
How can I change the feign URL (url="http://xxxx") during the runtime? because the URL can only be determined at run time.
You can add an unannotated URI parameter (that can potentially be determined at runtime) and that will be the base path that will be used for the request. E.g.:
#FeignClient(name = "dummy-name", url = "https://this-is-a-placeholder.com")
public interface MyClient {
#PostMapping(path = "/create")
UserDto createUser(URI baseUrl, #RequestBody UserDto userDto);
}
And then the usage will be:
#Autowired
private MyClient myClient;
...
URI determinedBasePathUri = URI.create("https://my-determined-host.com");
myClient.createUser(determinedBasePathUri, userDto);
This will send a POST request to https://my-determined-host.com/create (source).
Feign has a way to provide the dynamic URLs and endpoints at runtime.
The following steps have to be followed:
In the FeignClient interface we have to remove the URL parameter. We have to use #RequestLine annotation to mention the REST method (GET, PUT, POST, etc.):
#FeignClient(name="customerProfileAdapter")
public interface CustomerProfileAdaptor {
// #RequestMapping(method=RequestMethod.GET, value="/get_all")
#RequestLine("GET")
public List<Customer> getAllCustomers(URI baseUri);
// #RequestMapping(method=RequestMethod.POST, value="/add")
#RequestLine("POST")
public ResponseEntity<CustomerProfileResponse> addCustomer(URI baseUri, Customer customer);
#RequestLine("DELETE")
public ResponseEntity<CustomerProfileResponse> deleteCustomer(URI baseUri, String mobile);
}
In RestController you have to import FeignClientConfiguration
You have to write one RestController constructor with encoder and decoder as parameters.
You need to build the FeignClient with the encoder, decoder.
While calling the FeignClient methods, provide the URI (BaserUrl + endpoint) along with rest call parameters if any.
#RestController
#Import(FeignClientsConfiguration.class)
public class FeignDemoController {
CustomerProfileAdaptor customerProfileAdaptor;
#Autowired
public FeignDemoController(Decoder decoder, Encoder encoder) {
customerProfileAdaptor = Feign.builder().encoder(encoder).decoder(decoder)
.target(Target.EmptyTarget.create(CustomerProfileAdaptor.class));
}
#RequestMapping(value = "/get_all", method = RequestMethod.GET)
public List<Customer> getAllCustomers() throws URISyntaxException {
return customerProfileAdaptor
.getAllCustomers(new URI("http://localhost:8090/customer-profile/get_all"));
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<CustomerProfileResponse> addCustomer(#RequestBody Customer customer)
throws URISyntaxException {
return customerProfileAdaptor
.addCustomer(new URI("http://localhost:8090/customer-profile/add"), customer);
}
#RequestMapping(value = "/delete", method = RequestMethod.POST)
public ResponseEntity<CustomerProfileResponse> deleteCustomer(#RequestBody String mobile)
throws URISyntaxException {
return customerProfileAdaptor
.deleteCustomer(new URI("http://localhost:8090/customer-profile/delete"), mobile);
}
}
I don`t know if you use spring depend on multiple profile.
for example: like(dev,beta,prod and so on)
if your depend on different yml or properties. you can define FeignClientlike:(#FeignClient(url = "${feign.client.url.TestUrl}", configuration = FeignConf.class))
then
define
feign:
client:
url:
TestUrl: http://dev:dev
in your application-dev.yml
define
feign:
client:
url:
TestUrl: http://beta:beta
in your application-beta.yml (I prefer yml).
......
thanks god.enjoy.
use feign.Target.EmptyTarget
#Bean
public BotRemoteClient botRemoteClient(){
return Feign.builder().target(Target.EmptyTarget.create(BotRemoteClient.class));
}
public interface BotRemoteClient {
#RequestLine("POST /message")
#Headers("Content-Type: application/json")
BotMessageRs sendMessage(URI url, BotMessageRq message);
}
botRemoteClient.sendMessage(new URI("http://google.com"), rq)
You can create the client manually:
#Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
#Autowired
public FooController(ResponseEntityDecoder decoder, SpringEncoder encoder, Client client) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "http://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "http://PROD-SVC");
}
}
From documentation: https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#_creating_feign_clients_manually
In interface you can change url by Spring annotations. The base URI is configured in yml Spring configuration.
#FeignClient(
name = "some.client",
url = "${some.serviceUrl:}",
configuration = FeignClientConfiguration.class
)
public interface SomeClient {
#GetMapping("/metadata/search")
String search(#RequestBody SearchCriteria criteria);
#GetMapping("/files/{id}")
StreamingResponseBody downloadFileById(#PathVariable("id") UUID id);
}
Use #PathVariable like this:
#Service
#FeignClient(name = "otherservicename", decode404 = true)
public interface myService {
#RequestMapping(method = RequestMethod.POST, value = "/basepath/{request-path}")
ResponseEntity<String> getResult(#RequestHeader("Authorization") String token,
#RequestBody HashMap<String, String> reqBody,
#PathVariable(value = "request-path") String requestPath);
}
Then from service, construct the dynamic url path and send the request:
String requestPath = "approve-req";
ResponseEntity<String> responseEntity = myService.getResult(
token, reqBody, requestPath);
Your request url will be at: "/basepath/approve-req"
I prefer to build feign client by configuration to pass a url at run time (in my case i get the url by service name from consul discovery service)
so i extend feign target class as below:
public class DynamicTarget<T> implements Target<T> {
private final CustomLoadBalancer loadBalancer;
private final String serviceId;
private final Class<T> type;
public DynamicTarget(String serviceId, Class<T> type, CustomLoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
this.serviceId = serviceId;
this.type = type;
}
#Override
public Class<T> type() {
return type;
}
#Override
public String name() {
return serviceId;
}
#Override
public String url() {
return loadBalancer.getServiceUrl(name());
}
#Override
public Request apply(RequestTemplate requestTemplate) {
requestTemplate.target(url());
return requestTemplate.request();
}
}
var target = new DynamicTarget<>(Services.service_id, ExamsAdapter.class, loadBalancer);
package commxx;
import java.net.URI;
import java.net.URISyntaxException;
import feign.Client;
import feign.Feign;
import feign.RequestLine;
import feign.Retryer;
import feign.Target;
import feign.codec.Encoder;
import feign.codec.Encoder.Default;
import feign.codec.StringDecoder;
public class FeignTest {
public interface someItfs {
#RequestLine("GET")
String getx(URI baseUri);
}
public static void main(String[] args) throws URISyntaxException {
String url = "http://www.baidu.com/s?wd=ddd"; //ok..
someItfs someItfs1 = Feign.builder()
// .logger(new FeignInfoLogger()) // 自定义日志类,继承 feign.Logger
// .logLevel(Logger.Level.BASIC)// 日志级别
// Default(long period, long maxPeriod, int maxAttempts)
.client(new Client.Default(null, null))// 默认 http
.retryer(new Retryer.Default(5000, 5000, 1))// 5s超时,仅1次重试
// .encoder(Encoder)
// .decoder(new StringDecoder())
.target(Target.EmptyTarget.create(someItfs.class));
// String url = "http://localhost:9104/";
//
System.out.println(someItfs1.getx(new URI(url)));
}
}

Get AccessToken from spring cloud zuul API Gateway

We are using zuul as API gateway in spring cloud. Now we want to extract access token from zuul for further implementation.Please provide suggestion how we want to implement. Thank you
To read the authorization header you will need to create a filter in ZUUL my thought is you will need a pre filter you can change it based on your need. Here is what you will need.
public class TestFilter extends ZuulFilter {
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
final RequestContext ctx = RequestContext.getCurrentContext();
final HttpServletRequest request = ctx.getRequest();
//Here is the authorization header being read.
final String xAuth = request.getHeader("Authorization");
//Use the below method to add anything to the request header to read downstream. if needed.
ctx.addZuulRequestHeader("abc", "abc");
return null;
}
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
}
You will need a #Bean declaration for Filter in the class where you have #EnableZuulProxy
#Bean
public TestFilter testFilter() {
return new TestFilter();
}
Hope this helps.!!!

delegate HTTP request to Jersey

I have a nano HTTP based web server, that is supposed to delegate its calls to a jersey 2.22.2. On the webserver class constructor I declare an ApplicationHandler as a instance variable:
ApplicationHandler newHandler;
Then in the constructor I initilize it and register a Sample resource class:
Object[] instances = new Object[1];
instances[0] = new SampleResource();
ResourceConfig app = new ResourceConfig();
app.registerInstances(instances);
newHandler = new ApplicationHandler(app);
On the method that processes Http requests I create a ContainerRequest and execute the apply method on the application handler :
SecurityContext secContext = new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return "user";
}
};
}
#Override
public boolean isUserInRole(String s) {
return true;
}
#Override
public boolean isSecure() {
return true;
}
#Override
public String getAuthenticationScheme() {
return null;
}
};
PropertiesDelegate propertiesDelegate = new PropertiesDelegate() {
Map<String, Object> props = new HashMap<>();
#Override
public Object getProperty(String s) {
return props.get(s);
}
#Override
public Collection<String> getPropertyNames() {
return props.keySet();
}
#Override
public void setProperty(String s, Object o) {
props.put(s, o);
}
#Override
public void removeProperty(String s) {
props.remove(s);
}
};
ContainerRequest request = new ContainerRequest(new URI("http://localhost:2000"), new URI("/test"), session.getMethod().toString(), secContext, propertiesDelegate);
Future<ContainerResponse> responseFuture = newHandler.apply(request);
ContainerResponse response = responseFuture.get();
Object entity = response.getEntity();
Below is the code for the SampleResource class :
public class SampleResource {
#GET
#Path("test")
public Response testMethod() {
return Response.status(Response.Status.OK).build();
}
}
The main reason for doing this is that I want to call a custom API that injects objects into the annotated resource classes.
Stepping through the code, all I get is a NotFoundException.
If you want to inject custom Objects to the resources class you can do that in two waus
using #Context -- By adding your custom object to application context
usign #Inject -- By binding the application to resource config
to use #Context , you need to extend the java.security.Principal object and declare your object fields, and you can instantiate and assign values by using security context.
to user #InJect , you need to register org.glassfish.hk2.utilities.binding.AbstractBinder like below
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("org.foo.rest;org.bar.rest");
register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(ObjectThatneedtoInject.class).to(yourClass.class);
}
});
}
}

CXF REST: How can I retrieve the POJO object from the message in an interceptor before it gets marshal'd?

We have implemented a REST API in CXF. My goal is to be able to define custom annotations on a POJO and process them in a CXF interceptor before they get marshal'd. I believe I have all the information I need to be able to do this except for retrieving the actual object in the interceptor. My code looks like this:
Resource class
#Path("/mypath")
public class MyResource {
#GET
public MyObject getObject() {
MyObject o = new MyObject();
...
return o;
}
}
MyObject
public class MyObject {
private String x;
#MyAnnotation
public String getX() {
return x;
}
public String setX(x) {
this.x = x;
}
}
Interceptor
public class MyInterceptor extends AbstractPhaseInterceptor<Message> {
public VersionOutInterceptor() {
super(Phase.POST_LOGICAL);
}
public final void handleMessage(Message message) {
// 1. STUCK -- get object from the message
// 2. parse annotations and manipulate the object
// 3. put the object back on the message for serialization
}
}
How do I get the object from the message, manipulate it based on the annotations, and put it back on the message?
I have similar requirement and this is how I could do it
for In Interceptor I have used PRE_INVOKE Phase and for Out Interceptor PRE_LOGICAL Phase.
This code shows only logging but you can change the object if needed by Usecase.
code as below will fetch you the object you are looking for
#Override
public void handleMessage(Message message) throws Fault {
MessageContentsList objs = MessageContentsList.getContentsList(message);
if (objs != null && objs.size() == 1) {
Object responseObj = objs.get(0);
DomainPOJO do= (DomainPOJO)responseObj;
_logger.info(do.toString());
}
}