How to update the settings of an HttpClient in httpclient 4.3+? - java

In httpclient 4.3, if you respect all the deprecations, you must build and configure your HttpClient using an HttpClientBuilder (documentation here). The methods are explicit, they seem easy to use, and HttpClient's interface is clear. But maybe a tad too much.
In my own case, I have to inherit Spring's HttpComponentsHttpInvokerRequestExecutor (documentation here). As a consequence, I can easily get the HttpClient, but all I know about this object is that it implements the interface.
Since the client is already built, and I do not know anything about its implementation, I cannot access methods such as AbstractHttpClient's setHttpRequestRetryHandler or addRequestInterceptor (though yes, I know, they are deprecated).
So, what would be the cleanest way of updating the settings of this HttpClient (the retry handler and request interceptor are those I am most concerned with at the moment)? Should I...
... savagely cast my client to AbstractHttpClient, hoping I will always receive this implementation?
... create a new HttpClient in my HttpInvokerRequestExecutor's constructor, and get something like the example reproduced below? I might add that Spring's constructor (in 3.2.4 at least) uses methods deprecated in httpclient 4.3 too. Would there be any side effect I missed using this strategy?
... do something I have not proposed yet?
Custom constructor example:
public CustomHttpInvokerRequestExecutor() {
super(); // will create an HttpClient
// Now overwrite the client the super constructor created
setHttpClient(HttpClientBuilder.custom(). ... .build());
}

do something I have not proposed yet?
My recommendation would be to re-think the entire approach. One should not be removing / adding protocol interceptors at runtime but rather should be making use of HttpContext instance to update request execution context and pass configuration to protocol interceptors
http://hc.apache.org/httpcomponents-core-4.4.x/tutorial/html/fundamentals.html#d5e306
http://hc.apache.org/httpcomponents-client-4.3.x/tutorial/html/fundamentals.html#d5e223
CloseableHttpClient client = HttpClientBuilder.create()
.addInterceptorFirst(new HttpRequestInterceptor() {
#Override
public void process(
final HttpRequest request,
final HttpContext context) throws HttpException, IOException {
boolean b = (Boolean) context.getAttribute("my-config");
if (b) {
// do something useful
}
}
})
.build();
HttpClientContext context = HttpClientContext.create();
context.setAttribute("my-config", Boolean.TRUE);
CloseableHttpResponse response1 = client.execute(new HttpGet("/"), context);
response1.close();
context.setAttribute("my-config", Boolean.FALSE);
CloseableHttpResponse response2 = client.execute(new HttpGet("/"), context);
response2.close();

Related

WebClient logging without deprecated method tcpClient.bootstrap()

I'm trying do some logging in my middleware application, where I want to log request and response of backend system. I would like to use Netty (because we have implemented difficult SslContext).
I'm trying to learn it by this tutorial: https://www.baeldung.com/spring-log-webclient-calls (chapter 4.2. Logging with Netty HttpClient).
For better looking logs, there is part of code:
HttpClient httpClient = HttpClient
.create()
.tcpConfiguration(
tc -> tc.bootstrap(
b -> BootstrapHandlers.updateLogSupport(b, new CustomLogger(HttpClient.class))))
.build()
but bootstrap(...) method is deprecated. What can I use instead?
If you need CustomLogger for request/response (as per comment above) then you can do the following:
CustomLogger customLogger = new CustomLogger(HttpClient.class);
HttpClient httpClient = HttpClient
.create()
.doOnRequest((request, connection) -> {
connection.addHandlerFirst(customLogger);
});

How to disable cookies with Spring WebClient

Does anyone know if there's a way to disable cookie management in Spring WebClient using Reactor Netty HttpClient?
I noticed both WebClient.Builder and HttpClient APIs provide a means to add cookies to an outbound request but I am looking for a way to inhibit them altogether if such exists. That is akin to disableCookieManagement on Apache's HttpComponentClientBuilder.
It looks like there's no way to disable the cookie handling per se but this seems to work: Create your own HttpClient, then use HttpClient.doOnRequest to register a callback to be called before sending the request. In the callback, call HttpClientRequest.requestHeaders() to retrieve the request headers, then delete the Cookie header.
Sample code that removes User-Agent header before sending the request.
public class Main {
public static void main(String[] args) {
HttpClient httpClient = HttpClient.create().doOnRequest((request, connection) -> {
request.requestHeaders().remove("User-Agent");
});
WebClient client = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Mono<String> r = client.get().uri("https://www.google.com").retrieve()
.bodyToMono(String.class);
System.out.println(r.block());
}
}
In my testing, I'm not seeing that Reactor Netty HttpClient is doing anything at all with the cookies that come back in the response. I was actually looking to enable a cookie store like the Apache HttpClient supports, but to all indications the Netty HttpClient has no such feature.
In the entire codebase of Reactor Netty, there is no usage of the "set-cookie" header except in one of their unit tests. This tells me cookies are ignored by default unless the client code chooses to look for them.

Is there an easy way to manually force a caching HttpClient 4.3.x to bypass the cache?

I am using CachingHttpClientBuilder to create a CloseableHttpClient in by an HttpServlet to fetch internal resources. In most cases I want it to use its cache, and that seems to be working fine. However, periodically the client is used to check for updates to a special remote file (basically it has configuration data). In that case, I would like to be able to tell the client to fetch the resource via HTTP even if the cache contains it and it isn't marked as stale yet.
When I've run into situations like this in JavaScript I have often appended a bogus query string parameter with a timestamp in it so that the URL would not match a cached entry. However, I would think there is a better solution in this case since we have direct, programmatic access to the HTTP client.
Lest someone suggest just changing the remote server so that it sets a Cache-Control: no-cache header to prevent this, please understand that this is outside of my control. The goal is to bypass the cache in this instance regardless of whether or not the remote server says it can/should be cached.
Someone might also suggest not using a caching HttpClient in this case, which would be OK, but it seems less than ideal to me because then I would need to make a second HttpClient (other portions of this application require caching of these HTTP resources in order to perform sufficiently well).
EDIT: user3360944 suggested using a HttpCacheInvalidator, which may be the right solution, but I am not sure yet how to do this. Could someone give an example of what to put in the flushInvalidatedCacheEntries method to remove a cached entry for a given URL? (I have particular URLs that I never want to cache)
new HttpCacheInvalidator() {
#Override
public void flushInvalidatedCacheEntries(HttpHost host, HttpRequest req) {
// What do I need to do here to invalidate a cached entry for
// say, http://www.example.com/path/file.txt?
}
#Override
public void flushInvalidatedCacheEntries(HttpHost host, HttpRequest request, HttpResponse response) {
// Do nothing here since I don't need to invalidate anything
// based on the response received
}
}
It will involve a bit of custom code but one can bypass the caching layer entirely by slightly tweaking HttpClient's execution pipeline
class ConditionalCachingExec implements ClientExecChain {
private final ClientExecChain mainExec;
private final ClientExecChain cachingExec;
public ConditionalCachingExec(final ClientExecChain mainExec, final ClientExecChain cachingExec) {
this.mainExec = mainExec;
this.cachingExec = cachingExec;
}
#Override
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext clientContext,
final HttpExecutionAware execAware) throws IOException, HttpException {
URI uri = request.getURI();
if ("/stuff".equals(uri.getPath())) {
return mainExec.execute(route, request, clientContext, execAware);
} else {
return cachingExec.execute(route, request, clientContext, execAware);
}
}
};
class MyCachingHttpClientBuilder extends CachingHttpClientBuilder {
#Override
protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) {
ClientExecChain cachingExec = super.decorateMainExec(mainExec);
return new ConditionalCachingExec(mainExec, cachingExec);
}
};
CloseableHttpClient httpClient = new MyCachingHttpClientBuilder().build();
Since you have the CachingHttpClientBuilder, you can configure it to use a specific HttpCacheInvalidator with this method
setHttpCacheInvalidator(HttpCacheInvalidator cacheInvalidator)
You can use the HttpCachedInvalidator to invalidate a request prior to issuing it if you want the uncached version of the response.

Non-standard HTTP methods and Apache Http Components

I'm writing an HTTP server using the HttpCore library of Apache HTTPComponents 4.3 (Java). My server must be able to receive requests that have non-standard HTTP methods (methods other than GET, POST, DELETE, etc).
But when my server receives such a request, it returns a "method not supported" response. Is there a way to force HTTPComponents to accept non-standard HTTP methods?
Background: I'm working on implementing a WebDAV server, which uses non-standard methods (like MKCOL and PROPFIND).
I found the solution, so I will answer my own question. xD
You have to create your own HttpRequestFactory implementation, and pass it up the chain.
HttpRequestFactory reqFact = new HttpRequestFactory() {
public HttpRequest newHttpRequest(final RequestLine requestline) throws MethodNotSupportedException {
return new BasicHttpEntityEnclosingRequest(requestline);
}
public HttpRequest newHttpRequest(final String method, final String uri) throws MethodNotSupportedException {
return new BasicHttpEntityEnclosingRequest(method, uri);
}
};
HttpMessageParserFactory<HttpRequest> parserFact = new DefaultHttpRequestParserFactory(null, reqFact);
HttpConnectionFactory<DefaultBHttpServerConnection> connFact = new DefaultBHttpServerConnectionFactory(null, parserFact, null)
The implementation that HttpComponents uses by default throws a MethodNotSupportedException if a non-standard HTTP method is found. The source code for the default implementation can be found here:
http://hc.apache.org/httpcomponents-core-4.3.x/httpcore/xref/org/apache/http/impl/DefaultHttpRequestFactory.html

Apache HTTP 4.x: How can the "default context" be configured on HttpClient

In Apache HTTP Client (4.x, the successor to commons http 3.x), on HttpClient the method:
HttpClient.execute(HttpUriRequest request)
States in the JavaDocs:
"Executes a request using the default context."
What is the default context (referring to an HttpContext object)?
How can I configure the default context so I don't need to pass it on each call to execute()? (I don't control the call to execute(), but I control creating the HttpClient)
The default context is configured by the HttpClient implementation that you are using. For implementations based on AbstractHttpClient, the work is done by the createHttpContext() method. Note that a new default context is created for each execute call.
One way to configure the default context yourself would be to extend one of the existing HttpClient implementation classes and override the method.
Another way is to set the various parameters that the method uses; e.g. the connection manager's scheme registry, the authScheme registry, the cookieSpecs registry, the cookie store or the credentials provider.
For the record, here's what the DefaultHttpClient.createHttpContext() does:
#Override
protected HttpContext createHttpContext() {
HttpContext context = new BasicHttpContext();
context.setAttribute(
ClientContext.SCHEME_REGISTRY,
getConnectionManager().getSchemeRegistry());
context.setAttribute(
ClientContext.AUTHSCHEME_REGISTRY,
getAuthSchemes());
context.setAttribute(
ClientContext.COOKIESPEC_REGISTRY,
getCookieSpecs());
context.setAttribute(
ClientContext.COOKIE_STORE,
getCookieStore());
context.setAttribute(
ClientContext.CREDS_PROVIDER,
getCredentialsProvider());
return context;
}
From looking at the source code for AbstractHttpClient that creates the default HttpContext, you can control the values that it is created with through setting attributes on the HttpClient instance, by invoking setCredentialsProvider(CredentialsProvider credsProvider) for instance. Is there any particular property(ies) that you are looking to configure?

Categories