I was trying to understand the use of InterceptingAsyncClientHttpRequestFactory. When run test with MockRestServiceServer I saw requestFactory is decorated with this ResquestFactory. Is there any other use of this requestFactory? Basically I want to know the idea behind InterceptingAsyncClientHttpRequestFactory. As I couldn't find any examples to use it.
Below code doesn't work.
AsyncClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsAsyncClientHttpRequestFactory(
httpAsyncClient);
List<AsyncClientHttpRequestInterceptor> interceptors = new ArrayList<>(1);
interceptors.add(asyncRestReqResInterceptor());
AsyncClientHttpRequestFactory interceptorFactory = new InterceptingAsyncClientHttpRequestFactory(
clientHttpRequestFactory, interceptors);
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(interceptorFactory);
Please let me know the correct implementation.
The general idea behind request factory is that you can customize how HttpAccessor implementations, like RestTemplate, makes requests. If you don't specify anything you get non-pooled HttpURLConnection, but spring offers factories for Appache HttpClient and other 3rd part libraries.
InterceptingAsyncClientHttpRequestFactory is a decorator, which allows you to plug-in Interceptors that modify the request befor it is sent. One such Interceptor is the BasicAuthorizationInterceptor which adds the Authorization header to every request. If you have stuff you need to do to every request you can create your own ClientHttpRequestInterceptor.
Related
I have one question regarding Spring WebClient
In my application I need to do many similar API calls, sometimes I need change headers in the calls (Authentication token). So the question arises, what would be better of the two options:
To create one WebClient for all incoming requests to MyService.class, by making it private final field, like code below:
private final WebClient webClient = WebClient.builder()
.baseUrl("https://another_host.com/api/get_inf")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
Here arises another question: is WebClient thread-safe? (because service is used by many threads)
To create new WebClient for each new request incoming to service class.
I want to provide maximum performance, and to use it in right way, but I don't know how WebClient works inside it, and how it expects to be used.
Thank you.
Two key things here about WebClient:
Its HTTP resources (connections, caches, etc) are managed by the underlying library, referenced by the ClientHttpConnector that you can configure on the WebClient
WebClient is immutable
With that in mind, you should try to reuse the same ClientHttpConnector across your application, because this will share the connection pool - this is arguably the most important thing for performance. This means you should try to derive all WebClient instances from the same WebClient.create() call. Spring Boot helps you with that by creating and configuring for you a WebClient.Builder bean that you can inject anywhere in your app.
Because WebClient is immutable it is thread-safe. WebClient is meant to be used in a reactive environment, where nothing is tied to a particular thread (this doesn't mean you cannot use in a traditional Servlet application).
If you'd like to change the way requests are made, there are several ways to achieve that:
configure things in the builder phase
WebClient baseClient = WebClient.create().baseUrl("https://example.org");
configure things on a per-request basis
Mono<ClientResponse> response = baseClient.get().uri("/resource")
.header("token", "secret").exchange();
create a new client instance out of an existing one
// mutate() will *copy* the builder state and create a new one out of it
WebClient authClient = baseClient.mutate()
.defaultHeaders(headers -> {headers.add("token", "secret");})
.build();
From my experience, if you are calling an external API on a server you have no control over, don't use WebClient at all, or use it with the pooling mechanism turned off. Any performance gains from connection pooling are greatly overweighed by the assumptions built into the (default reactor-netty) library that will cause random errors on one API call when another was abruptly terminated by the remote host, etc. In some cases, you don't even know where the error occurred because the calls are all made from a shared worker thread.
I made the mistake of using WebClient because the doc for RestTemplate said it would be deprecated in the future. In hindsight, I would go with regular HttpClient or Apache Commons HttpClient, but if you are like me and already implemented with WebClient, you can turn off the pooling by creating your WebClient as follows:
private WebClient createWebClient(int timeout) {
TcpClient tcpClient = TcpClient.newConnection();
HttpClient httpClient = HttpClient.from(tcpClient)
.tcpConfiguration(client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout * 1000)
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(timeout))));
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
*** Creating a separate WebClient does not mean that WebClient will have a separate connection pool. Just look at the code for HttpClient.create - it calls HttpResources.get() to get the global resources. You could provide the pool settings manually but considering the errors that occur even with the default setup, I don't consider it worth the risk.
I am using
#RequestHeader(value = "channel") String channel
in Spring application controller to get headers and it works fine for normal headers, however, in some cases, I need to trap a request using filter and use HttpServletRequestWrapper to add some extra headers. The new headers added by overriding getHeader method are not being accepted by #RequestHeader annotation and throws error. However, if I manually get headers using
HttpServletRequest.getHeader("channel")
the new headers work fine. Is there any bug in #RequestHeader implementation? If so, is there any work-around so that I wouldn't have to change the same thing in 50+ APIs.
When resolving #RequestHeader, Spring is using getHeaders(), not getHeader(). You need to override this one.
I am using the RestTemplate class to get to some page, which will require redirect ( redirect to oauth 2 provider, but that probably doesnt matter ).
I am doing:
ResponseEntity<String> forEntity = oAuth2RestTemplate.getForEntity(url, String.class, Collections.emptyMap());
with standard OAuth2RestTemplate, I haven't done any custom creation of this bean, so it is just injected from Spring.
As a response I get a ResponseEntity with location header and 302 status, but it is not followed. Judging by many other sources RestTemplate should follow redirects by default if all I do is GET - similar issue here: Follow 302 redirect using Spring restTemplate?
This is not a case, no redirect happens, am I missing something? Do I have to manually grab this location and redirect to it manually?
update:
OK, OAuth2RestTemplate is using OAuth2AccessTokenSupport as a request factory. This class configures factory to NOT follow redirects. Too bad it is so hard to find.
But using = new RestTemplate() doesnt help either ( note new keyword, if you are going to autowire it, then Spring will autowire oauth2RestTemplate ).
I'm using Okhttp3 and I want to build an OAuth2 Authenticator.
Sometimes, I need to make http requests from the Authenticator itself (ie: to refresh the token) but the api doesn't provide a way to do it.
For sure, I can create a new okhttp instance but I don't know if it is a recommanded practice.
Is it a best practice for my need?
It is not possible to do it out of the box but some workarounds can work:
create a new instance of OkHttpClient into the Authenticator, or
add a setHttpClient method in Authenticator
.
MyAuthenticator authenticator = new MyAuthenticator();
OkHttpClient client = new OkHttpClient.Builder()
.authenticator(authenticator)
.build();
authenticator.setHttpClient(client);
From: https://github.com/square/okhttp/issues/2733
I'm using Spring Data REST to build a RESTful API. Until now my HTML GUI for this RESTful service was served from the same Tomcat and I had no problems wit Cross Origin requests.
Now I want to serve the static files from a different server. This means the API is on another domain/port. Browsers will send the OPTIONS request to get the Access-Control headers from the server. Unfortunately Spring Data REST does not handle those OPTIONS requests and even returns a HTTP 500.
I tried creating a custom controller that handles all OPTIONS requests
#Controller
#RequestMapping(value = "/**", method = RequestMethod.OPTIONS)
public class OptionsController {
#RequestMapping
public ResponseEntity options() {
return new ResponseEntity<Void>(HttpStatus.OK);
}
}
Which worked for OPTIONS, but then all other requests (like GET) ceased to work.
OPTIONS requests are switched on via the dispatchOptionsRequest dispatcher servlet parameter.
tl;dr: currently Spring Data REST does not answer OPTIONS requests at all.
It might be worth opening a ticket in our JIRA.
Browsers will send the OPTIONS request to get the Access-Control headers from the server.
Is that specified somewhere? If so, it would be cool if the ticket description included a link to that spec.
A few comments regarding your approach for a workaround:
#RequestMapping on the controller method overrides the method attribute and expectedly now matches all HTTP methods, which is why you see all requests intercepted. So you need to define OPTIONS as HTTP method there, too (or maybe instead of in the class mapping).
You're not returning any Allow header which is the whole purpose of OPTIONS in the first place.
I wonder if the approach in general makes sense as it'll be hard to reason about the supported HTTP methods in general.
Just set the parameter dispatchOptionsRequest to true into the dispatcher to process the Options method calls, into the implementation of the WebApplicationInitializer.
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(applicationContext));
dispatcher.setInitParameter("dispatchOptionsRequest", "true");
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/*");