I am working with retrofit and need to be able to use multiple interceptors. Currently I am using one to automatically append an auth token but i need to be able to make calls with no auth token. If i add another interceptor with no auth token in the header how do I use that one instead of the auth token interceptor.
val interceptor: Interceptor = Interceptor { chain ->
val newRequest = chain.request().newBuilder().
addHeader("Auth_Token", pref.getString(PSPreferences.prefAuthKey, "")).
cacheControl(CacheControl.FORCE_NETWORK).
build()
chain.proceed(newRequest)
}
okHttpClient = OkHttpClient.Builder().
readTimeout(1, TimeUnit.MINUTES).
connectTimeout(1, TimeUnit.MINUTES).
addInterceptor(interceptor).build()
val retrofitInstance = Retrofit.Builder()
.baseUrl(APIEndpointInterface.BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
apiInterface = retrofitInstance.create<APIEndpointInterface>(APIEndpointInterface::class.java)
OkHttpClient maintains a list of the interceptors which you can access, however it is an unmodifiable collection.
This leaves us with three options I believe:
Create two OkHttpClient instances, and by deduction two Retrofit
instances, one for the unauthenticated requests, and one for the
authenticated requests.
Check if you should use the interceptor, e.g. in your authentication interceptor, you can first check if there exists a key in your preferences for the token, and if so use it; if not, you simply proceed without modifying anything. You do this for your unauthenticated interceptor too. I think this is the easiest solution for your case.
Create a single interceptor, which will maintain a modifiable list
of interceptors which you can add and remove at will. You would need
to keep a reference to this interceptor, maybe make it a Singleton.
For the third option, I have provided a very simple example:
public class HttpRequestResponseInterceptor implements Interceptor {
public final List<RequestInterceptor> requestInterceptors = new ArrayList<>();
public final List<ResponseInterceptor> responseInterceptors = new ArrayList<>();
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
for (RequestInterceptor interceptor : requestInterceptors) {
request = interceptor.intercept(request);
}
Response response = chain.proceed(request);
for (ResponseInterceptor interceptor : responseInterceptors) {
response = interceptor.intercept(response);
}
return response;
}
public interface RequestInterceptor {
Request intercept(Request request) throws IOException;
}
public interface ResponseInterceptor {
Response intercept(Response response) throws IOException;
}
}
In this case you would need to implement the custom interfaces RequestInterceptor and ResponseInterceptor.
An example of what an implementation of these interfaces would look like:
public class ExampleInterceptor implements HttpRequestResponseInterceptor.RequestInterceptor,
HttpRequestResponseInterceptor.ResponseInterceptor {
#Override
public Request intercept(Request request) throws IOException {
return request.newBuilder().addHeader("REQUEST_HEADER", "EXAMPLE").build();
}
#Override
public Response intercept(Response response) throws IOException {
return response.newBuilder().addHeader("RESPONSE_HEADER", "EXAMPLE").build();
}
}
You would then need to add this interceptor to our main interceptor twice, once to requestInterceptors and once to responseInterceptors (or only to one of these if it intercepts only requests or only responses).
This example is far from complete. The benefit of this solution is that it adds the ability to add and remove interceptors without having to recreate the OkHttpClient instance. It requires extra work if you want to support retrying requests, for example.
Related
So far, I've not seen any solutions that is working for me. I've tried this and this.
Here is my custom filter:
#Component
public class TestFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyFilter;
#Autowired
private rewriteBody bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).map(ex -> {
GatewayFilter delegate = modifyFilter.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
delegate.filter(exchange, chain);
return ex;
});
}
#Override
public int getOrder() {
return -1;
}
#Component
public class rewriteBody implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
byte[] newBody = "New response".getBytes();
return Mono.just(newBody);
}
}
}
The ModifyResponseBodyGatewayFilterFactory works if I implement it in a pre-filter, but how do I modify it in the post-filter.
To answer the question about modifying the response in post-filter. Firstly, need to understand the pre and post filters developed in Spring Cloud Gateway.
There is no specific separation for pre and post filters in Spring Cloud Gateway by any interface or any other component. It is simply 'How logic has been written for the same filter'.
If any logic written before chain.filter(exchange) method call are executed before running another 'filter in chain' or 'calling target service endpoint'. Since, the logic/code running before making call to another filter or target endpoint, it is called pre-filter and used for pre-processing like adding additional headers, security assertions, rate limiting and so on.
If any logic written after chain.filter(exchange) method call are executed after the processing completed in chain.filter(exchange) method, means the 'target service endpoint' has been completed and then the logic/lines written after chain.filter(exchange) is being executed. Therefore, it is called post-filter.
Since, it is just way of writing and placement of code decides whether it is for pre/post both can be written in single filter.
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.just(exchange)
.map(it -> {
it.getRequest().getHeaders().add("x-pre-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Pre-Processing/PreFilter");
})
.map(it -> chain.filter(it))
.then()
.map(it -> {
exchange.getResponse().getHeaders().add("x-post-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Post-Processing/PostFilter");
});
}
Additionally, sequence of execution of filters are controlled by ordering of filters.
Now, when question comes for modifying response body, it is very clear that response will be provided only when 'target service endpoint' called which requires chain.filter(exchange).
Here there is a twist, called 'response commit'. When response is already committed, cannot change in response body and as soon as chain.filter(exchange) is called, it will take micro/mili-seconds to write response to client and commit the response. Means, if any code written after then() method makes changes in response body it will throw exception 'response already committed'.
To avoid it, response body always modified while making chain.filter(exchange) call. Example, consider code written in ModifyResponseBodyGatewayFilterFactory's method filter(...) as:
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate()
.response(new ModifiedServerHttpResponse(exchange, config)).build());
}
Here, exchange is mutated and response body is set at the same moment. It will modify the response and invoke other filters in chain, if there is no any remaining filters in chain then it serve the response to client.
So conceptually, response body modification happens as post activity only it the filter comes later in chain. Needs like other filter should not be executed once response body modified / some specific filter needs to be executed after response body modification need to be managed by Filter's ordering.
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
I am using Feign to make my http call. For security reason, I need to generate and add header on each request.
Headers values may differ on each request (for example, it use current connected user in session).
With Feign we can define a requestInterceptor to handle header generation.
But my problem, I that a requestInterceptor is defined when FeignBuilder is used. But after building my client, I can't access easily to this interceptor (except keep a reference on it).
Ex:
MyApi api = Feign.builder()
.requestInterceptor(new SecuringRequestInterceptor("some static value"))
.target(MyApi.class, "http://....");
How can I add some per request specific value to this interceptor to let it generate some kind of header based on these values ?
I have try to keep a reference on the requestInterceptor instance and create a method to update his status. But I don't think it is thread safe in a concurrent environment :
class SecuringRequestInterceptor implements RequestInterceptor {
private String staticValue;
private String dynamicValue;
public SecuringRequestInterceptor(String value) {
this.staticValue = value;
}
public void update(String value) {
this.dynamicValue = dynamicValue;
}
#Override
public void apply(RequestTemplate template) {
// use dynamic value here
template.header("sign", this.staticValue + "_" + this.dynamicValue);
}
}
thanks for your help.
I am trying to learn about Retrofit since it seems to take care of a lot of the issues I am currently having with JSON requests and handling.
first and foremost, I understand that the methods we use are defined inside of interfaces, while making simple requests to obtain data it is quite simple to specify what is to be retrieved from the url as well as all the necessary endpoints based on the famous github example.
So if we are retrieving information form the github api, we would first create all the necessary pojo models and such and then define the interface as:
public interface GithubService {
#GET("users/{username}")
Observable<Github>getGithHubUser(#Path("username")String userName);
}
From that on the main activity we would have something like:
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.github.com/")
.build();
GithubService githubService = retrofit.create(GithubService.class);
Observable<Github> githubUser = githubService.getGithHubUser("usersName");
githubUser.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(user -> "Github Username: " + user.getName() + "\nUrl:" +user.getUrl() + "\nfollowing: "+ user.getHireable())
.subscribe(userInfo -> Log.d("Output", userInfo));
My question here would be how to send JSON information if the url requires something like this:
"data={\"process\":\"procesNumber\", \"phone\":\"123456\"}"
Basically, in order to get any response form the server I have been doing this using simple okhttp:
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(CREATE_MEDIA_TYPE, "data={\"process\":\"procesNumber\", \"phone\":\"123456\"}");
String ALLWAYS_API = "http://something something bla bla";
Request request = new Request.Builder()
.url("https://blablabla")
.post(body)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
... etc etc etc
}
To my understanding, even I need to create a pojo class that represents the data that needs to be sent to retrofit, something along the lines of:
public class DataRequest {
final String proces;
final String phone;
DataRequest(String process, String phone) {
this.process = process;
this.phone = phone;
}
}
Which would comply to the information being sent to the request, but how would I actually parse that to the interface implementation?
interface DataService {
#Post(not a clue what to place here)
DataRequest postJson(#Body how?)
}
And how would I actually add that to the retrofit builder? The examples that I am using come from different forums on the web as well as other questions asked by other users, this one in particular helped a lot in understanding a couple of things: How to POST raw whole JSON in the body of a Retrofit request? but I still don't understand where everything goes and some of the other questions and examples are far too complex for what I need to do.
Ok, so in order to leave an answer here for anyone trying to do this. By default, retrofit comes with many utilities which handle the passing of data as JSON, but in this case what I am passing is a string that looks like json inside of a tag called data......I know..
But in order to answer this for the people facing similar issues, in order to pass in the string we need to import a scalar convertor much in the same way that we need to import a gson converter to work with our retrofit services:
compile 'com.squareup.retrofit2:converter-scalars:2.0.2'
After that, our service can be handled as:
public interface CreateService {
#Headers({ "Content-Type: application/x-www-form-urlencoded;charset=UTF-8"})
#POST("your/post/path/goes/here")
Call<String> getStringScalar(#Body String body);
}
I write my service generators in a separate file, in this case, the whole thing can be used in this way:
public class ServiceGeneratorWithScalarConvertor {
private static final String API_BASE_URL = "your/base/url/goes/here";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// basically, this code is the same as the one from before with the added instance of creating and making use of the scalar converter factory.....scratch that i took it off
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
public static <S> S createService(Class<S> serviceClass) {
builder.client(httpClient.build());
Retrofit retrofit = builder.build();
return retrofit.create(serviceClass);
}
}
From there, we can access the results with this particular method(i am using this method inside my main activity:
public void retroFitCreateAPIExample() {
CreateService service = ServiceGeneratorWithScalarConvertor.createService(CreateService.class);
String body = "data={\"process\":\"process1\",\"phone\":\"12345\"}";
Call<String> call = service.getStringScalar(body);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if(response.isSuccessful()){
Log.d("Response Body>>>>>", response.body());
createR = new Gson().fromJson(response.body().toString(), CreateModels.class);
Log.d("CREATED RESPONSE",createR.getCreate().getStops().get(0).getCity());
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
}
});
}
The instance is this passed to the service generator that uses scalar convertors, the body of the post request is saved as a simple string(as it was specified in the interface) and we can do with the response whatever we want.
I have created a ClientHttpRequestInterceptor that I use to intercept all outgoing RestTemplate requests and responses. I would like to add the interceptor to all outgoing Feign requests/responses. Is there a way to do this?
I know that there is a feign.RequestInterceptor but with this I can only intercept the request and not the response.
There is a class FeignConfiguration that I found in Github that has the ability to add interceptors but I don't know in which maven dependency version it is.
A practical example of how to intercept the response in a Spring Cloud OpenFeign.
Create a custom Client by extending Client.Default as shown below:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
Response response = super.execute(request, options);
InputStream bodyStream = response.body().asInputStream();
String responseBody = StreamUtils.copyToString(bodyStream, StandardCharsets.UTF_8);
//TODO do whatever you want with the responseBody - parse and modify it
return response.toBuilder().body(responseBody, StandardCharsets.UTF_8).build();
}
}
Then use the custom Client in a configuration class:
public class FeignClientConfig {
public FeignClientConfig() { }
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}
Finally, use the configuration class in a FeignClient:
#FeignClient(name = "api-client", url = "${api.base-url}", configuration = FeignClientConfig.class)
public interface ApiClient {
}
Good luck
If you want to use feign from spring cloud, use org.springframework.cloud:spring-cloud-starter-feign as your dependency coordinates. Currently the only way to modify the response is to implement your own feign.Client.